---
# System prepended metadata

title: Quy hoạch động (Phần 4) - Lời giải

---

---
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;
}
```
:::
