# DP學習筆記
[toc]
## 背包問題
現在有 n 個物品,第 i 個物品的重量和價值分別是 w_i 和 v_i,妳的背包最多可以裝 W 的重量, 問能裝進背包的最大價值為何?
$1 \le n \le 100$
$1 \le w_i \le W \le 10^5$
$1 \le v_i \le 10^9$
**解法:**
在前i個物品中,重量為$w$的最大價值為何?
$F(i, w) = \text{max}(\space F(i - 1, w), F(i-1, w-w_i\space) + v_i)$
選:
```cpp=
#include <bits/stdc++.h>
using namespace std;
using lint = long long int;
#define endl "\n";
int n, max_w, t1, t2;
vector<pair<lint, lint>> vec; // weight, value
vector<vector<lint>> dp(1000, vector<lint>(100010, -1));
lint f(lint i, lint w)
{
if(i == -1 || w < 0) return 0;
if(dp[i][w] != -1)
{
return dp[i][w];
}
else
{
lint choose = 0;
lint not_choose = f(i-1, w);
if(w - vec[i].first >= 0)
{
choose = f(i-1, w - vec[i].first) + vec[i].second;
}
dp[i][w] = max(choose, not_choose);
return dp[i][w];
}
}
int main()
{
cout.sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> max_w;
for (int i = 0; i < n; i++)
{
cin >> t1 >> t2;
vec.emplace_back(t1, t2);
}
cout << f(n-1, max_w);
}
```
## 消除位數
給一個數字 n,每次操作可以減掉 n 的某個 位數 的值。問最少要幾次操作才能讓 n 歸零
$1 \le n \le 10^6$
**解法:**
$F(0) = 0$
$F(i) = \text{min}(\space f(i - d) + 1 \space)$, $d$ 是 $i$ 的位數
```cpp=
#include <bits/stdc++.h>
using namespace std;
using lint = long long int;
#define endl "\n";
vector<int> dp((int)1e6 + 10, -1);
// 當前數字為 i 時,減位數到 0 的最小步數
int f(int i)
{
if(i <= 0) return 0;
if(dp[i] != -1)
{
return dp[i];
}
else
{
string string_i = to_string(i);
int count = string_i.size(); // 得到 string_i 的位數
int min = (int)1e8, current_num;
for (int j = 0; j < count; j++)
{
if(string_i[j] == '0')
continue;
current_num = f(i - (string_i[j] - '0')) + 1;
if(current_num < min)
{
min = current_num;
}
}
dp[i] = min;
return dp[i];
}
}
int main()
{
cout.sync_with_stdio(false);
cin.tie(nullptr);
int i;
cin >> i;
cout << f(i);
}
```
## 找零問題
### Minimizing Coins 湊錢問題
給定 𝑛 種面額不同的錢幣,問要湊出 𝑥 元最少需要幾個錢幣?
$1 \le n \le 100$
$1 \le x \le 10^6$
$1 \le c_i \le 10^6$
**解法**:
湊出 i 元最少需要幾個硬幣
$F(i) = \text{min}(\space F(i - c_i) \space) + 1$
**範例**:
湊出 5 元最少需要幾個硬幣(2元,3元)
湊出 5 元 =>拿2元 => 湊出 3 元 => 拿2元 => 湊出 1 元 => 不可能
=========================> 拿3元 => (2枚)
```cpp=
#include <bits/stdc++.h>
using namespace std;
using lint = long long int;
#define endl "\n";
#define INF (int)1e7 + 10
vector<int> vec;
vector<int> dp((int)1e6 + 10, -1);
int n, target, t;
// 湊出 i 元最少需要幾個硬幣
int f(int i)
{
if(i == 0) return 0;
if(i < 0) return INF;
if(dp[i] != -1)
{
return dp[i];
}
else
{
int ans = INF;
for (int j = 0; j < n; j++)
{
ans = min(f(i - vec[j]) + 1, ans);
}
dp[i] = ans;
return dp[i];
}
}
int main()
{
cout.sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> target;
for (int i = 0; i < n; i++)
{
cin >> t;
vec.emplace_back(t);
}
int ans = f(target);
if(ans == INF)
cout << -1;
else
cout << ans;
}
```
## LCS 最長共同子序列
假設有兩個序列:
序列 A 長度為 m,表示為 A = a1, a2, ..., am
序列 B 長度為 n,表示為 B = b1, b2, ..., bn
我們定義一個二維 DP 陣列 dp[i][j],表示 A 的前 i 個字符和 B 的前 j 個字符的最長公共子序列的長度。
> LCS 的子序列是指可以不連續出現的元素順序,而不要求是連續的子串。
**解法:**
\begin{flalign}
F(i,j ) &= 0, &\text{when i = 0 or j = 0} \\
&= F(i - 1, j - 1) + 1, &\text{when i = j} \\
&= max(\space F(i-1, j), \space F(i, j-1)) &\text{when i} \neq \text{j}
\end{flalign}
**範例**:
"**abc**dkdkdj" 和 "anddnf**abc**" 的 LCS 是 "abc" 長度 3
```cpp=
#include <bits/stdc++.h>
using namespace std;
using lint = long long int;
=
vector<vector<int>> dp(1010, vector<int>(1010, -1));
string s1, s2;
// 長度 i 和 長度 j 的 LCS 長度
int f(int i, int j)
{
if(i <= 0 || j <= 0) return 0;
if(dp[i][j] != -1)
{
return dp[i][j];
}
else
{
if(s1[i-1] == s2[j-1])
{
dp[i][j] = f(i-1, j-1) + 1;
return dp[i][j];
}
else
{
dp[i][j] = max(f(i-1, j), f(i, j-1));
return dp[i][j];
}
}
}
int main()
{
cout.sync_with_stdio(false);
cin.tie(nullptr);
while(cin >> s1)
{
cin >> s2;
cout << f(s1.size(), s2.size()) << endl;
}
}
```
# 最大子陣列和 MSS
**解法:**
$F(i) = \text{max}(F(i-1) + a_i \space ,\space a_i)$
```cpp=
#include <bits/stdc++.h>
using namespace std;
using lint = long long int;
lint n, t;
vector<lint> vec;
lint dp[(int)2e7 + 10] = {0};
lint maxN;
lint f(lint i)
{
if(i == 1) return vec[0];
if(dp[i] != (lint)-1e18)
{
return dp[i];
}
else
{
dp[i] = max(f(i-1) + vec[i-1], vec[i-1]);
maxN = maxN < dp[i] ? dp[i] : maxN ;
return dp[i];
}
}
int main()
{
cout.sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 0; i < (int)2e7; i++)
{
dp[i] = -1e18;
}
for (int i = 0; i < n; i++)
{
cin >> t;
vec.emplace_back(t);
}
maxN = vec[0];
f(n);
cout << maxN;
}
```