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