--- title Quy hoạch động (Phần 4) - Lời giải --- Quy hoạch động (Phần 4) - Lời giải === --- ###### ✍️ Author: 2School Guideline ###### 📋 Content: [TOC] --- # A. [Edit Distance](https://cses.fi/problemset/task/1639) ## Đề bài - Cho 2 xâu $s, t$ - Ta có thể thực hiện 3 loại thao tác sau đây: - Thêm 1 kí tự vô xâu - Xóa 1 kí tự khỏi xâu - Thay đổi một kí tự ở trong xâu - Hãy tìm số thao tác ít nhất để làm cho xâu $s$ và xâu $t$ bằng nhau ### Input - Dòng đầu chứa 1 xâu có $n$ kí tự - Dòng tiếp theo chứa 1 xâu có $m$ kí tự ### Output - In ra số thao tác tối thiểu cần thực hiện ### Giới hạn - $1 \le n,\space m \le 5000$ ### Sample Input ``` LOVE MOVIE ``` ### Sample Output ``` 2 ``` ## Lời giải :::spoiler **Ý tưởng** - $a[:i]$: xâu con của xâu $a$ gồm $i$ kí tự đầu tiên - Gọi $dp(i,\space j)$ là số thao tác cần thực hiện ít nhất để 2 tiền tố $s[:i]$ và $t[:j]$ bằng nhau - Công thức truy hồi của bài toán: - Trường hợp 1: Xóa kí tự $s_i$ sẽ tốn 1 thao tác, và ta vẫn cần biến đổi xâu $s[:i-1]$ thành $t[:j]$ nên sẽ tốn $1 + dp(i-1,\space j)$ thao tác - Trường hợp 2: Thêm kí tự $t_j$ vào cuối xâu $s$ sẽ tốn 1 thao tác, và ta vẫn cần biến đổi xâu $s[:i]$ thành $t[:j-1]$ nên sẽ tốn $1 + dp(i,\space j-1)$ thao tác - Trường hợp 3: Thay đổi kí tự $s_i$ thành $t_j$ sẽ tốn 1 thao tác, và ta vẫn cần biến đổi xâu $s[:i-1]$ thành $t[:j-1]$ nên sẽ tốn $1 + dp(i-1,\space j-1)$ thao tác - Trường hợp 4: $s_i = t_j$, nên ta chỉ cần biến đổi xâu $s[:i-1]$ thành $t[:j-1]$ và tốn $dp(i-1,\space j-1)$ thao tác. Đây có thể coi như là thao tác biến đổi $s_i$ thành $t_j$ nhưng không mất chi phí do 2 kí tự đã bằng nhau. - Cập nhật $dp(i,\space j)$ bằng trường hợp tốn chi phí ít nhất - Độ phức tạp: $O(|s| * |t|)$ ::: ::: spoiler **Code mẫu** ```cpp #include <bits/stdc++.h> using namespace std; int main() { ios_base::sync_with_stdio(false); cin.tie(0); string s, t; cin >> s >> t; int n = s.size(), m = t.size(); s = "#" + s; t = "#" + t; vector <vector<int>> dp(n+3, vector<int>(m+3, 0)); for (int i = 1; i <= n; ++i) dp[i][0] = i; for (int i = 1; i <= m; ++i) dp[0][i] = i; for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { int cost1 = dp[i-1][j] + 1; int cost2 = dp[i][j-1] + 1; int cost3 = dp[i-1][j-1] + 1; int cost4 = 1e10; if (s[i] == t[j]) cost4 = dp[i-1][j-1]; dp[i][j] = min({cost1, cost2, cost3, cost4}); } } cout << dp[n][m] << "\n"; return 0; } ``` ::: # B. [Array Description](https://cses.fi/problemset/task/1746) ## Đề bài - Cho 1 mảng gồm $n$ phần tử có giá trị trong khoảng $[1, m]$. Giá trị tuyệt đối của hiệu các phần tử nằm cạnh nhau tối đa là $1$ (nói cách khác là $|a_i - a_{i-1}| \le 1$ với $2 \le i \le n$) - Mảng được cho sẽ có 1 vài phần tử chưa xác định giá trị. Hãy xác định có bao nhiêu mảng thỏa điều kiện đề bài cho. ### Input - Dòng đầu gồm 2 số nguyên $n$ và $m$: số phần tử của mảng và giá trị tối đa của các phần tử trong mảng. - Dòng tiếp theo gồm $n$ số nguyên $x_1, x_2, x_3,..., x_n$: giá trị của mảng, giá trị $0$ đại diện cho phần tử chưa xác định giá trị. ### Output - In ra một dòng duy nhất là số mảng tìm được modulo $10^9 + 7$ ### Sample Input ``` 3 5 2 0 2 ``` ### Sample Output ``` 3 ``` ### Giải thích - Các mảng $[2, 1, 2],\space [2, 2, 2],\space [2, 3, 2]$ là các mảng tìm được thỏa điều kiện đề bài. ## Lời giải :::spoiler **Ý tưởng** Chúng ta xử lý trường hợp khi $i$ $=$ $0$ riêng biệt. Nếu $x[0]$ $=$ $0$, ta có thể thay thế nó bằng bất cứ giá trị nào ($dp[0][v]$ $=$ $1$ cho tất cả $v$). Ngược lại, nếu $x[0]$ $=$ $v$ $\ne$ 0 , thì $dp[0][v]$ $=$ $1$ là giá trị duy nhất được phép. Bây giờ đến các chỉ số khác $i > 0$ . Nếu $x[i]$ $=$ $0$ , chúng ta có thể thay thế nó bằng bất kỳ giá trị nào. Tuy nhiên, nếu chúng ta thay thế nó bằng $v$ , giá trị trước đó phải là $v-1$ , $v$ hoặc $v+1$ . Do đó, số cách để điền vào mảng đến $i$ là tổng của giá trị trước đó là $v-1$ , $v$ và $v+1$ . Nếu x[i] = v từ đầu vào, chỉ có $dp[i][v]$ được phép (nghĩa là $dp[i][j]$ $=$ $0$ nếu j $\ne$ v). Tuy nhiên, **$dp[i][v]$ $=$ $dp[i-1][v-1]$ $+$ $dp[i-1][v]$ $+$ $dp[i-1][v+1]$**. Độ phức tạp là $O(n * m)$ trong trường hợp xấu nhất khi x toàn bộ là số $0$ ::: ::: spoiler **Code mẫu** ```cpp #include <bits/stdc++.h> using namespace std; int main() { int mod = 1e9+7; int n, m; cin >> n >> m; vector<vector<int>> dp(n,vector<int>(m+1,0)); int x0; cin >> x0; if (x0 == 0) { fill(dp[0].begin(), dp[0].end(), 1); } else { dp[0][x0] = 1; } for (int i = 1; i < n; i++) { int x; cin >> x; if (x == 0) { for (int j = 1; j <= m; j++) { for (int k : {j-1,j,j+1}) { if (k >= 1 && k <= m) { (dp[i][j] += dp[i-1][k]) %= mod; } } } } else { for (int k : {x-1,x,x+1}) { if (k >= 1 && k <= m) { (dp[i][x] += dp[i-1][k]) %= mod; } } } } int ans = 0; for (int j = 1; j <= m; j++) { (ans += dp[n-1][j]) %= mod; } cout << ans << endl; } ``` :::