【C++】競程筆記(多維 DP、LCS 最長共同子序列 習題練習)練習題目參考自:NTUCPC Guide,此筆記僅為個人學習用途。
823 . RGBProblem Source:https://oj.ntucpc.org/problems/823
先定義三種顏色的代號以及對分數的貢獻(權重):
紅色(j = 0 j=0 j = 0 ):權重為 − v i -v_i − v i (題目要求扣掉紅色數字總和)。 綠色(j = 1 j=1 j = 1 ):權重為 + v i +v_i + v i (題目要求加上綠色數字總和)。 藍色(j = 2 j=2 j = 2 ):權重為 0 0 0 (藍色不影響分數)。 最後再來定義狀態:定義 d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 為考慮到第 i i i 個數字,且將第 i i i 個數字塗上顏色 j j j 時,目前所能獲得的最大價值。
i : 1 ≤ i ≤ N i : 1 \le i \le N i : 1 ≤ i ≤ N j : 0 ≤ j ≤ 2 j : 0 \le j \le 2 j : 0 ≤ j ≤ 2 (分別代表紅、綠、藍)求 DP 轉移式:
題目限制「相鄰的數字必須標上不同的顏色」,要計算第 i i i 個數字塗某個顏色時,第 i − 1 i-1 i − 1 個數字必須是另外兩種顏色之一,最終解答是從中選取較大者,再加上當前顏色的權重。
對第 i i i 個數字 (i > 1 i > 1 i > 1 ):
若第 i i i 個塗紅色(j = 0 j=0 j = 0 ):前一個必須是綠色或藍色。$$dp[i][0] = \max(dp[i-1][1], dp[i-1][2]) - v_i$$ 若第 i i i 個塗綠色(j = 1 j=1 j = 1 ):前一個必須是紅色或藍色。$$dp[i][1] = \max(dp[i-1][0], dp[i-1][2]) + v_i$$ 若第 i i i 個塗藍色(j = 2 j=2 j = 2 ):前一個必須是紅色或綠色。$$dp[i][2] = \max(dp[i-1][0], dp[i-1][1])$$ 初始狀態定義:
當考慮序列中第 1 個數字(i = 1 i=1 i = 1 )時,沒有前一個數字的限制,直接根據顏色計算價值。
d p [ 1 ] [ 0 ] = − v 1 dp[1][0] = -v_1 d p [ 1 ] [ 0 ] = − v 1 d p [ 1 ] [ 1 ] = + v 1 dp[1][1] = +v_1 d p [ 1 ] [ 1 ] = + v 1 d p [ 1 ] [ 2 ] = 0 dp[1][2] = 0 d p [ 1 ] [ 2 ] = 0 最終答案:在塗完最後一個數字 N N N 後,最後一個數字可能是紅、綠、藍任一種,取三者中的最大值即為答案:$$Answer = \max(dp[N][0], dp[N][1], dp[N][2])$$
時間複雜度: O ( N ) O(N) O ( N )
空間複雜度: O ( N ) O(N) O ( N )
記得 DP 陣列要是 long long,題目測資有點大。
範例程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <bits/stdc++.h> using namespace std;const int MAXN = 1000005 ;long long dp[MAXN][3 ];int main () { ios::sync_with_stdio (false ), cin.tie (nullptr ); int N; cin >> N; int a[N + 5 ]; for (int i = 1 ; i <= N; ++i) cin >> a[i]; dp[1 ][0 ] = -a[1 ]; dp[1 ][1 ] = a[1 ]; dp[1 ][2 ] = 0 ; for (int i = 1 ; i <= N; ++i){ dp[i][0 ] = max (dp[i - 1 ][1 ], dp[i - 1 ][2 ]) - a[i]; dp[i][1 ] = max (dp[i - 1 ][0 ], dp[i - 1 ][2 ]) + a[i]; dp[i][2 ] = max (dp[i - 1 ][0 ], dp[i - 1 ][1 ]); } cout << max ({dp[N][0 ], dp[N][1 ], dp[N][2 ]}) << '\n' ; }
Grid 1Problem Source:https://atcoder.jp/contests/dp/tasks/dp_h
狀態定義:d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 為從起點 ( 1 , 1 ) (1, 1) ( 1 , 1 ) 走到座標 ( i , j ) (i, j) ( i , j ) 的路徑總數。 i i i (row),範圍 1 ∼ H 1 \sim H 1 ∼ H 。j j j (column),範圍 1 ∼ W 1 \sim W 1 ∼ W 。轉移式定義: 題目要求只能「向右」或「向下」移動。
要到達 ( i , j ) (i, j) ( i , j ) ,只可能從上方 ( i − 1 , j ) (i-1, j) ( i − 1 , j ) 或左方 ( i , j − 1 ) (i, j-1) ( i , j − 1 ) 走過來。
對於每格 ( i , j ) (i, j) ( i , j ) ,判斷規則如下:
( i , j ) (i, j) ( i , j ) 是障礙物(#):無法到達該格,路徑數為 0。$$dp[i][j] = 0$$( i , j ) (i, j) ( i , j ) 是空地(.):路徑數等於「從上方來的路徑數」加上「從左方來的路徑數」。$$dp[i][j] = dp[i-1][j] + dp[i][j-1]$$初始狀態定義: d p [ 1 ] [ 1 ] = 1 dp[1][1] = 1 d p [ 1 ] [ 1 ] = 1 ,因為一開始就在 ( 1 , 1 ) (1, 1) ( 1 , 1 ) ,算 1 種方法(停在原地)。 需要注意的是,當跑雙層迴圈,i == 1 且 j == 1 的情況要被跳過,否則後續計算會錯誤,結果會是 0。
另外在讀取 m 的字元時,當 j = W,會讀到字串結尾 \0,透過 j - 1 可以避開。
或是可以在輸入的時候做 padding(填充),就可以直接寫 m[i][j]:
1 2 3 4 5 for (int i = 1 ; i <= H; ++i) { string temp; cin >> temp; m[i] = " " + temp; }
範例程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <bits/stdc++.h> using namespace std;const int MOD = 1000000007 ;int main () { ios::sync_with_stdio (false ), cin.tie (nullptr ); int H, W; cin >> H >> W; vector <string> m (H + 1 ); for (int i = 1 ; i <= H; ++i) cin >> m[i]; vector <vector <long long >> dp (H + 1 , vector <long long >(W + 1 , 0 )); dp[1 ][1 ] = 1 ; for (int i = 1 ; i <= H; ++i){ for (int j = 1 ; j <= W; ++j){ if (i == 1 && j == 1 ) continue ; if (m[i][j - 1 ] == '#' ){ dp[i][j] = 0 ; } else { dp[i][j] = (dp[i - 1 ][j] + dp[i][j - 1 ]) % MOD; } } } cout << dp[H][W] << '\n' ; }
P2386 放蘋果Problem Source:https://www.luogu.com.cn/problem/P2386
狀態定義: d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 是將 i i i 顆蘋果放入 j j j 個盤子的方法數。
轉移式定義:
至少一個盤子為空:既然盤子是相同的,空哪個都沒差。可先將一個盤子移走,不用它。將 i i i 個蘋果放入 j − 1 j-1 j − 1 個盤子。 $$dp[i][j - 1]$$ 所有盤子都有放蘋果(無空盤):每個盤子至少要有一個蘋果。 可先在每個盤子上各放 1 1 1 個蘋果(消耗掉 j j j 個蘋果)。 剩下的蘋果數量為 i − j i - j i − j 個。 問題轉化為「將剩下的 i − j i-j i − j 個蘋果,放入 j j j 個盤子(可隨意放)」。$$dp[i-j][j], i \ge j$$ 最終得到 $$dp[i][j] = dp[i][j-1] + dp[i-j][j]$$
當 i < j i < j i < j 時,因為不可能每個盤子都放蘋果,第二項為 0,即 d p [ i ] [ j ] = d p [ i ] [ j − 1 ] dp[i][j] = dp[i][j-1] d p [ i ] [ j ] = d p [ i ] [ j − 1 ]
初始狀態定義:沒有蘋果(i = 0 i=0 i = 0 ):$$dp[0][j] = 1$$ 只有 1 個盤子(j = 1 j=1 j = 1 ):$$dp[i][1] = 1$$ 範例程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> using namespace std;int main () { ios::sync_with_stdio (false ), cin.tie (nullptr ); int t; cin >> t; while (t--){ int m, n; cin >> m >> n; int dp[15 ][15 ] = {0 }; for (int j = 0 ; j <= n; ++j) dp[0 ][j] = 1 ; for (int i = 0 ; i <= m; ++i) dp[i][1 ] = 1 ; for (int i = 1 ; i <= m; ++i){ for (int j = 1 ; j <= n; ++j){ if (i < j){ dp[i][j] = dp[i][j - 1 ]; } else { dp[i][j] = dp[i][j - 1 ] + dp[i - j][j]; } } } cout << dp[m][n] << '\n' ; } }
N 箱 M 球Problem Source:https://tioj.ck.tp.edu.tw/problems/1291
狀態定義:d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 為將 i i i 個不同的球,放入 j j j 個相同的箱子,且沒有空箱的方法數。i i i 代表當前處理到的球數(1 ≤ i ≤ m 1 \le i \le m 1 ≤ i ≤ m )j j j 代表當前使用的箱子數(1 ≤ j ≤ n 1 \le j \le n 1 ≤ j ≤ n ) 轉移式定義:考慮第 i i i 顆球放入時的情況,此時已用 j j j 個箱子,第 i i i 顆球有兩種選擇:自己獨佔一個新箱子:代表前 i − 1 i-1 i − 1 顆球已佔用了 j − 1 j-1 j − 1 個箱子,第 i i i 顆球放入第 j j j 個箱子。方法數為:$$dp[i-1][j-1]$$ 放入已經有球的箱子中:代表前 i − 1 i-1 i − 1 顆球已佔用了 j j j 個箱子,第 i i i 顆球可以放入這 j j j 個箱子中的任一個。因為這 j j j 個箱子裡面已有不同的球了,所以這 j j j 個箱子被視為不同的箱子。方法數為:$$j \times dp[i-1][j]$$ 最終得到:$$dp[i][j] = (dp[i-1][j-1] + j \times dp[i-1][j]) \pmod{1000000}$$
初始狀態定義:
d p [ 0 ] [ 0 ] = 1 dp[0][0] = 1 d p [ 0 ] [ 0 ] = 1 :0 個球放入 0 個箱子,視為一種方法(空集合)。最終答案:$$ans = \sum_{k=1}^{n} dp[m][k] \pmod{1000000}$$
因為題目這邊要求的是使用 1 個箱子到使用 n n n 個箱子的所有情況總和。
範例程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> #include <algorithm> using namespace std;const int MAXN = 205 ;const int MOD = 1000000 ;long long dp[MAXN][MAXN];int main () { ios::sync_with_stdio (false ), cin.tie (nullptr ); dp[0 ][0 ] = 1 ; for (int i = 1 ; i < MAXN; ++i){ for (int j = 1 ; j <= i; ++j){ dp[i][j] = (dp[i - 1 ][j - 1 ] + j * dp[i - 1 ][j]) % MOD; } } int n, m; while (cin >> n >> m && (n != 0 || m != 0 )){ long long ans = 0 ; for (int k = 1 ; k <= n && k <= m; ++k) ans = (ans + dp[m][k]) % MOD; cout << ans << '\n' ; } }
幸運表格Problem Source:https://oj.ntucpc.org/problems/790
狀態定義:d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 為從座標 ( i , j ) (i, j) ( i , j ) 出發,依照規則走到離開表格為止,所能獲得的最大幸運指數總和。 轉移式定義:為了讓總和最大,選「右」、「下」兩方向中值較大的那一個。向右走:如果右邊還有格子(j + 1 < M j+1 < M j + 1 < M ),得到$$dp[i][j+1]$$ 如果右邊是邊界(離開表格),得到$$0$$ 向下走:如果下面還有格子(i + 1 < N i+1 < N i + 1 < N ),得到 $$dp[i+1][j]$$ 如果下面就是邊界(離開表格),得到 $$0$$ 最終得到:$$dp[i][j] = A[i][j] + \max(\text{向右獲利}, \text{向下獲利})$$
當中 A [ i ] [ j ] A[i][j] A [ i ] [ j ] 是幸運表格的數字。
初始狀態定義:最右下角的格子 ( N − 1 , M − 1 ) (N-1, M-1) ( N − 1 , M − 1 ) 因為右邊和下面都是界外,所以 d p [ N − 1 ] [ M − 1 ] = A [ N − 1 ] [ M − 1 ] + max ( 0 , 0 ) dp[N-1][M-1] = A[N-1][M-1] + \max(0, 0) d p [ N − 1 ] [ M − 1 ] = A [ N − 1 ] [ M − 1 ] + max ( 0 , 0 ) 。 在程式設計上,由於計算 ( i , j ) (i, j) ( i , j ) 需要用到 ( i + 1 , j ) (i+1, j) ( i + 1 , j ) 和 ( i , j + 1 ) (i, j+1) ( i , j + 1 ) 的值,須由右下角往左上角計算。
範例程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <iostream> #include <vector> #include <algorithm> using namespace std;const int INF = -1e9 ;int main () { ios::sync_with_stdio (false ), cin.tie (nullptr ); int n, m; cin >> n >> m; vector <vector<int >> a (n, vector <int >(m, 0 )); vector <vector<int >> dp (n, vector <int >(m, 0 )); for (int i = 0 ; i < n; ++i) for (int j = 0 ; j < m; ++j) cin >> a[i][j]; int ans = INF; for (int i = n - 1 ; i >= 0 ; --i){ for (int j = m - 1 ; j >= 0 ; --j){ int right_gain = 0 ; if (j + 1 < m){ right_gain = dp[i][j + 1 ]; } int down_gain = 0 ; if (i + 1 < n){ down_gain = dp[i + 1 ][j]; } dp[i][j] = a[i][j] + max (right_gain, down_gain); if (dp[i][j] > ans){ ans = dp[i][j]; } } } cout << ans << '\n' ; }
APCS 2020/10 P3 勇者修煉Problem Source:https://oj.ntucpc.org/problems/789
狀態定義: 定義兩個輔助狀態陣列,及一個最終狀態陣列(假設目前處理到第 i i i 列):
L [ j ] L[j] L [ j ] :表示到達位置 ( i , j ) (i, j) ( i , j ) ,且最後一步是由上方 ( i − 1 , j ) (i-1, j) ( i − 1 , j ) 或是左方 ( i , j − 1 ) (i, j-1) ( i , j − 1 ) 走過來的最大經驗值。(向右掃描)R [ j ] R[j] R [ j ] :表示到達位置 ( i , j ) (i, j) ( i , j ) ,且最後一步是由上方 ( i − 1 , j ) (i-1, j) ( i − 1 , j ) 或是右方 ( i , j + 1 ) (i, j+1) ( i , j + 1 ) 走過來的最大經驗值。(向左掃描)d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] :表示從起點到達 ( i , j ) (i, j) ( i , j ) 的最終最大經驗值。在同一列中,一條路徑不可能同時「先向左再向右」或「先向右再向左」,這樣會經過同一格兩次,所以到達 ( i , j ) (i, j) ( i , j ) 的最佳路徑,必定是 L [ j ] L[j] L [ j ] 和 R [ j ] R[j] R [ j ] 兩者取其大。
轉移式定義: 假設已算完第 i − 1 i-1 i − 1 列的結果,記為 p r e v _ d p prev\_dp p r e v _ d p ,現在要計算第 i i i 列的值,該格的經驗值為 c o s t [ i ] [ j ] cost[i][j] c o s t [ i ] [ j ] 。
對於第 i i i 列的每一個位置 j j j :
向右掃描 L [ j ] L[j] L [ j ] :考慮從「上一列直接下來」比較好,還是從「這一列的左邊走過來」比較好。$$L[j] = cost[i][j] + \max(prev_dp[j], L[j-1])$$邊界條件:若是第一欄 j = 0 j=0 j = 0 ,則只能從上面下來,不能從左邊來。 向左掃描 R [ j ] R[j] R [ j ] :考慮從「上一列直接下來」比較好,還是從「這一列的右邊走過來」比較好。$$R[j] = cost[i][j] + \max(prev_dp[j], R[j+1])$$邊界條件:若是最後一欄 j = m − 1 j=m-1 j = m − 1 ,則只能從上面下來,不能從右邊來。 最後 d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 取兩者為最大值:$$dp[i][j] = \max(L[j], R[j])$$ 初始狀態定義:所有上面要用的陣列大小為 m,都設 0。 這邊採用的是滾動 DP 的方式解題,能節省空間。
範例程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <bits/stdc++.h> using namespace std;using ll = long long ;int main () { ios_base::sync_with_stdio (false ), cin.tie (nullptr ); int n, m; cin >> n >> m; vector<vector<int >> grid (n, vector <int >(m)); for (int i = 0 ; i < n; ++i) for (int j = 0 ; j < m; ++j) cin >> grid[i][j]; vector<ll> prev_dp (m, 0 ) ; vector<ll> L (m) , R (m) ; vector<ll> dp (m) ; for (int i = 0 ; i < n; ++i) { for (int j = 0 ; j < m; ++j) { ll from_up = prev_dp[j]; ll from_left = (j == 0 ) ? -1e18 : L[j - 1 ]; if (j == 0 ) L[j] = grid[i][j] + from_up; else L[j] = grid[i][j] + max (from_up, from_left); } for (int j = m - 1 ; j >= 0 ; --j) { ll from_up = prev_dp[j]; ll from_right = (j == m - 1 ) ? -1e18 : R[j + 1 ]; if (j == m - 1 ) R[j] = grid[i][j] + from_up; else R[j] = grid[i][j] + max (from_up, from_right); } for (int j = 0 ; j < m; ++j) dp[j] = max (L[j], R[j]); prev_dp = dp; } ll ans = -2e18 ; for (ll val : prev_dp) if (val > ans) ans = val; cout << ans << '\n' ; return 0 ; }
連鎖店 (Chains)Problem Source:https://tioj.ck.tp.edu.tw/problems/1938
題目中所謂每家都要開在前一家的東北方,意思就是N N N 個點的 X X X 與 Y Y Y 座標都必須是嚴格遞增。
狀態定義:d p [ k ] [ y ] dp[k][y] d p [ k ] [ y ] 為在考慮當前的 X X X 座標時,已選取了 k k k 家連鎖店,且第 k k k 家店位於 Y Y Y 座標為 y y y 的位置時,所有連鎖店累積的最大顧客數總和。 轉移式定義: 當遍歷到第 x x x 列時,嘗試在此列放置第 k k k 家店。
假設要在 ( x , y ) (x, y) ( x , y ) 放置第 k k k 家店,根據題目要求,第 k − 1 k-1 k − 1 家店必須位於 ( x ′ , y ′ ) (x', y') ( x ′ , y ′ ) ,其中 x ′ < x x' < x x ′ < x 且 y ′ < y y' < y y ′ < y 。
因為是由 x = 0 x = 0 x = 0 到 M − 1 M-1 M − 1 依序計算,當處理到 x x x 時,d p dp d p 表中儲存的正是 x ′ < x x' < x x ′ < x 的最佳解,因此只要注意 y y y 的限制(y ′ < y y' < y y ′ < y )即可。
可得轉移式:$$dp[k][y] = \max(dp[k][y], \quad \text{profit}(k, x, y) + \max_{0 \le y’ < y} { dp[k-1][y’] })$$
profit ( k , x , y ) = ( a ⋅ ( k − 1 ) + b ⋅ x + c ⋅ y ) m o d d \text{profit}(k, x, y) = (a \cdot (k-1) + b \cdot x + c \cdot y) \mod d profit ( k , x , y ) = ( a ⋅ ( k − 1 ) + b ⋅ x + c ⋅ y ) m o d d max 0 ≤ y ′ < y { d p [ k − 1 ] [ y ′ ] } \max_{0 \le y' < y} \{ dp[k-1][y'] \} max 0 ≤ y ′ < y { d p [ k − 1 ] [ y ′ ] } 代表在前幾列中,選了 k − 1 k-1 k − 1 家店且 Y Y Y 座標小於目前 y y y 的最大值。為了避免用到同一列 x x x 更新過的資料(x x x 不能相同),在實作時,內層迴圈計算 k k k 時應由大到小(N N N 到 1 1 1 ),或者使用暫存變數,確保所讀取的 d p [ k − 1 ] dp[k-1] d p [ k − 1 ] 是來自之前的 x x x 列。
初始狀態定義:所有 d p [ k ] [ y ] dp[k][y] d p [ k ] [ y ] 初始化為一個極小值。 範例程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <bits/stdc++.h> using namespace std;using ll = long long ;int main () { ios::sync_with_stdio (false ), cin.tie (nullptr ); int M, N; ll a, b, c, d; cin >> M >> N >> a >> b >> c >> d; vector <vector<ll>> dp (N + 1 , vector <ll>(M + 1 , -1 )); for (int x = 0 ; x < M; ++x){ for (int k = N; k >= 1 ; --k){ ll max_prev = -1 ; for (int y = 0 ; y < M; ++y){ ll profit = (a * (k - 1 ) + b * x + c * y) % d; if (k == 1 ) dp[k][y] = max (dp[k][y], profit); else if (max_prev != -1 ) dp[k][y] = max (dp[k][y], max_prev + profit); if (k > 1 ) max_prev = max (max_prev, dp[k - 1 ][y]); } } } ll ans = 0 ; for (int y = 0 ; y < M; ++y) if (dp[N][y] != -1 ) ans = max (ans, dp[N][y]); cout << ans << '\n' ; }
Edit DistanceProblem Source:https://cses.fi/problemset/task/1639
狀態定義: 令輸入的兩個字串分別為 S S S (長度 n n n )和 T T T (長度 m m m )。
d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] 為將字串 S S S 的前 i i i 個字元轉換成字串 T T T 的前 j j j 個字元,所需的最少操作次數。
S [ 1 … i ] S[1 \dots i] S [ 1 … i ] 表示 S S S 的前 i i i 個字元(index 1 based)。T [ 1 … j ] T[1 \dots j] T [ 1 … j ] 表示 T T T 的前 j j j 個字元。轉移式定義: 對於 d p [ i ] [ j ] dp[i][j] d p [ i ] [ j ] ,考慮 S S S 的第 i i i 個字元(S [ i ] S[i] S [ i ] )和 T T T 的第 j j j 個字元(T [ j ] T[j] T [ j ] )的關係。
可從三個方向轉移,分別對應三種操作:
插入:假設 S [ 1 … i ] S[1 \dots i] S [ 1 … i ] 已轉成 T [ 1 … j − 1 ] T[1 \dots j-1] T [ 1 … j − 1 ] ,只要在 S S S 的尾端插入一個字元 T [ j ] T[j] T [ j ] 即可變成 T [ 1 … j ] T[1 \dots j] T [ 1 … j ] 。$$dp[i][j-1] + 1$$ 刪除:假設 S [ 1 … i − 1 ] S[1 \dots i-1] S [ 1 … i − 1 ] 已轉成 T [ 1 … j ] T[1 \dots j] T [ 1 … j ] ,只要把 S S S 原本多出來的第 i i i 個字元刪除。$$dp[i-1][j] + 1$$ 替換:如果 S [ i ] = = T [ j ] S[i] == T[j] S [ i ] = = T [ j ] ,則不需要任何操作,直接繼承 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] d p [ i − 1 ] [ j − 1 ] 的結果。$$dp[i-1][j-1]$$ 如果 S [ i ] ≠ T [ j ] S[i] \neq T[j] S [ i ] = T [ j ] ,把 S S S 的第 i i i 個字元替換成 T T T 的第 j j j 個字元。$$dp[i-1][j-1] + 1$$ 最後得到轉移式:$$dp[i][j] = \min \begin{cases} dp[i][j-1] + 1 & \text{(插入)} \ dp[i-1][j] + 1 & \text{(刪除)} \ dp[i-1][j-1] + cost & \text{(替換)} \end{cases}$$
其中 c o s t cost c o s t 的定義為若 S [ i ] = = T [ j ] S[i] == T[j] S [ i ] = = T [ j ] 則 c o s t = 0 cost = 0 c o s t = 0 ,否則 c o s t = 1 cost = 1 c o s t = 1 。
初始狀態定義d p [ i ] [ 0 ] = i dp[i][0] = i d p [ i ] [ 0 ] = i :將 S S S 的前 i i i 個字元轉換成空字串,需要刪除 i i i 次。d p [ 0 ] [ j ] = j dp[0][j] = j d p [ 0 ] [ j ] = j :將空字串轉換成 T T T 的前 j j j 個字元,需要插入 j j j 次。 範例程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <bits/stdc++.h> using namespace std;int main () { ios::sync_with_stdio (false ), cin.tie (nullptr ); string s, t; cin >> s >> t; int n = s.length (), m = t.length (); vector <vector <int >> dp (n + 1 , vector <int >(m + 1 )); for (int i = 0 ; i <= n; ++i) dp[i][0 ] = i; for (int j = 0 ; j <= m; ++j) dp[0 ][j] = j; for (int i = 1 ; i <= n; ++i){ for (int j = 1 ; j <= m; ++j){ int cost = (s[i - 1 ] == t[j - 1 ]) ? 0 : 1 ; dp[i][j] = min ({ dp[i - 1 ][j] + 1 , dp[i][j - 1 ] + 1 , dp[i - 1 ][j - 1 ] + cost }); } } cout << dp[n][m] << '\n' ; }