---
# System prepended metadata

title: C++從零入門Level 3-2
tags: [文華社課]

---

---
tags: 文華社課
---
> 文華高中電腦研究社29th
> 編輯 : 黃昱凱、陳睿哲

# C++從零入門Level 3-2

---

## 動態規劃 DP (dynamic programming)
### 引言

我們在上一節課有提到說，有些情況下使用貪心演算法的答案不一定是正確的
面對這種題目時，**依照題目**，可以使用的方法有很多
像cdq分治、二分搜尋法、DFS、BFS、線段樹、BIT...等等等等
這些技巧幾乎都可以幫助我們達到**減少執行時間**的目的
一個常用且非常有效的方法，就是DP

### Dynamic Programming
Dynamic、在以前是抄筆記的意思
Programming、編程

DP的核心觀念在**如果有重複計算同樣結果的行為，就抄在筆記上，遇到直接拿出來用**
長話短說就是**一樣的事情不做第二遍**
把**問題分割成多個小問題、逐個擊破再重新組合答案**
是不是很有分治法的味道阿w

沒錯!其實DP就是只是有記憶的分治法
下面請看範例:


---

#### 費波納契數列
一數列，第一項和第二項為1，排成一列，**且該數字為前兩項數字的和**

1、 1、 2、 3、 5、 8、 13、 21、 34、 55、 89

**fib(n) = {fib(n-1)+fib(n-2),n >= 1,fib(1) = fib(2) = 1}**

> 沒有DP
> 時間複雜度O(2^n^)
```cpp=
int fib(int n){
    if (n == 1 or n == 2)
        return 1;
    return fib(n-1)+fib(n-2);
}
```

> DP
>  時間複雜度O(n)
```cpp=
int dp[1000] = {0};

int fib(int n){
    if (n == 1 or n == 2)
        return 1;
    if (dp[n] != 0) return dp[n];
    dp[n] = fib(n-1)+fib(n-2);
    return dp[n];
}
```


---

#### 實作範例 0-1背包問題

> 看螢幕

```cpp=
#include <iostream>
#include <string.h>

using namespace std;

int num;
int v[1005];
int k[1005];
int dp[1005][105];
int solve(int idx,int remain){

    if (idx == num or remain == 0){
        return 0;
    }

    else if (dp[idx][remain] != -1){
        return dp[idx][remain];
    }

    else if (remain < v[idx]){
        dp[idx][remain] = solve(idx+1,remain);
        return dp[idx][remain];
    }

    int take = solve(idx+1,remain-v[idx])+k[idx];
    int dont = solve(idx+1,remain);
    dp[idx][remain] = max(take,dont);

    return dp[idx][remain];
}

int main(){
    while (cin >> num){
        memset(dp,-1,sizeof(dp));
        for (int i = 0;i < num;i++){
            cin >> v[i] >> k[i];
        }
        int ans = solve(0,100);
        cout << ans << endl;
    }
}
```
---

#### 實作範例二 不能貪心的換錢問題

你們會好奇上堂課講到的換錢問題嗎~?
其實不難，就像上面講的，開一個筆記儲存結果即可

**題目:**

蘿莉國有面額各為1000、500、100、50、10、**7**、5、1的鈔票
現在有一個人拿了n元，且n的範圍如右: 0<n<10000
請輸出**有多少種可能為組合的鈔票呢~?**

一題兩用真爽(X


```cpp=
#include<bits/stdc++.h>
using namespace std;
long long int money[10000] = {0};
int arr[8] = {1, 5, 7, 10, 50, 100, 500, 1000};
int main(){
  money[0] = 1;
  for(int i = 0; i < 8; i++){
    for(int j = arr[i]; j <= 10000; j++){
      money[j] += money[j - arr[i]];
    }
  }
  int n;
  while(cin >> n){
    cout << money[n] << endl;
  }
  return 0;
}
```

範例輸入1:
```
8
```
範例輸出1:
```
3
```

輸出講解，有:、1元\*8、1元\*3 + 5元\*1 和 1元\*1 + 7元\*1這三種組合

範例輸入2:
```
8941
```
範例輸出2:
```
1892655596636
```

:::danger

- [ ] [d052: 11456 - Trainsorting](https://zerojudge.tw/ShowProblem?problemid=d052)
Tip: LIS + LDS dp

:::
:::danger
- [ ] [c147: 105北二5搬家規劃問題](https://zerojudge.tw/ShowProblem?problemid=c147)
DP，對所有可能價格找最佳解
:::

---
> 目錄: https://hackmd.io/@lolicon5208/HkSNa1kIY

## 參考答案
### d052
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define N 2005
int a[N] = {0};
int lis[N];
int lds[N];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int k;
    cin >>k;
    while(k --)
    {
        int n;
        cin >> n;
        int ans = 0;
        if (n == 0) {
                cout << 0 <<endl;
            continue;
        }
        for(int i = 0;i < n;i ++) cin >> a[i];
        for(int i = n - 1; i >= 0; i --)
        {
            lis[i] = 1;
            lds[i] = 1;
            for(int j = n - 1; j > i; j --)
            {
                if (a[i] > a[j]) lis[i] = max(lis[i], lis[j] + 1);
                if (a[i] < a[j]) lds[i] = max(lds[i], lds[j] + 1);
            }
            ans = max(ans, lis[i] + lds[i] - 1);
        }
        cout << ans <<endl;
    }
}
```
### c147
```cpp=
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define N 1005
#define MAX 1000005
int w[N], v[N], cnt = 0;
LL max_w;
int a[MAX] = {0};
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    string s1, s2;
    stringstream ss, sss;
    getline(cin, s1);
    ss << s1;
    while(ss >> s1){
        w[cnt ++] = stoi(s1);
    }
    getline(cin, s2);
    sss << s2;
    cnt = 0;
    while(sss >> s2){
        v[cnt ++] = stoi(s2);
    }
    cin >> max_w;
    for(int i = 0;i < cnt;i ++){
        for(int j = max_w;j > 0;j --){
            if (j - w[i] >= 0) a[j] = max(a[j], a[j-w[i]] + v[i]);
        }
    }
    cout << a[max_w]<<endl;
    return 0;
}
```