---
# System prepended metadata

title: DP學習筆記
tags: [C++學習]

---

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

