Basic
Dynamic Programming
日月卦長
先備知識
- High Dimensional Array
- Recursion
- Bitwise operator
- Math
Fibonacci number
Fn=Fn−1+Fn−2
F0=0F1=1
迴圈版
遞迴版
long long F[100+5];
// ...
F[0] = 0;
F[1] = 1;
for(int i=2; i<=100; ++i){
F[i] = F[i-1] + F[i-2];
}
// ...
cout << F[100] << '\n';
long long F(int i){
if(i == 0) return 0;
if(i == 1) return 1;
return F(i-1) + F(i-2);
}
// ...
cout << F(100) << '\n';
遞迴實驗
#include <bits/stdc++.h>
using namespace std;
long long F(int i){
if(i==0 || i==1) return i;
return F(i-1) + F(i-2);
}
int main(){
for(int i = 0; i < 50; ++i){
cout << i << ": " << F(i) << '\n';
}
return 0;
}
遞迴實驗2
#include <bits/stdc++.h>
using namespace std;
long long DP[1000];
long long F(int i){
if(i==0 || i==1) return i;
if(DP[i]!=0) return DP[i];
return DP[i] = F(i-1) + F(i-2);
}
int main(){
for(int i = 0; i < 50; ++i){
cout << i << ": " << F(i) << '\n';
}
return 0;
}
遞迴實驗2
#include <bits/stdc++.h>
using namespace std;
long long DP[1000];
long long F(int i){
if(i==0 || i==1) return i;
if(DP[i]!=0) return DP[i];
return DP[i] = F(i-1) + F(i-2);
}
int main(){
for(int i = 0; i < 50; ++i){
cout << i << ": " << F(i) << '\n';
}
return 0;
}
動態規劃充要條件
- 重覆子問題 Overlapping subproblems
- 最佳子結構 Optimal substructure
其實就是滿足一些條件的遞迴公式
重覆子問題
Fn
Fn−1
Fn−2
Fn−2
Fn−3
- 該問題在計算時會出現很多重覆子問題
- 我們稱每個子問題為:狀態
最佳子結構
- 該問題的最佳解可以從子問題的最佳解中求得
- 可以寫成公式
- 稱為:狀態轉移式
F0=0F1=1
Fn=Fn−1+Fn−2
時間複雜度計算
- 狀態數量 * 狀態轉移時間
F0=0F1=1
Fn=Fn−1+Fn−2
- 狀態數量: n
- 狀態轉移時間: O(1)
- 總時間: n×O(1)=O(n)
Bottom Up
Top Down
long long F[100+5];
// ...
F[0] = 0;
F[1] = 1;
for(int i=2; i<=100; ++i){
F[i] = F[i-1] + F[i-2];
}
// ...
cout << F[100] << '\n';
long long DP[100+5];
long long F(int i){
if(i == 0) return 0;
if(i == 1) return 1;
if(DP[i]!=0) return DP[i];
return DP[i] = F(i-1) + F(i-2);
}
// ...
cout << F(100) << '\n';
程式寫法
Bottom Up
- 只需要迴圈
- 速度快
- 清楚知道狀態轉移順序
- 可以做的優化較多
Top Down
- 遞迴
- 稍慢
- 想法簡單
動態規劃題思考步驟
- 覺得要用動態規劃
- 定義狀態
- 根據狀態想出狀態轉移式
- 檢查時間複雜度
- 寫成程式
背包問題
經典題
背包問題
- 一個背包能裝載重量 T
- 有 n 個物品重量分別為 c1,c2,…,cn
- 價值分別為 w1,w2,…,wn
- 至多能裝入總價值多少的東西?
定義狀態
- 設d(i,v)表示:
- 編號1∼i的物品
- 選一些放入大小為v的背包中
- 最多可以得到的價值
狀態轉移
- d(i,v)=0 if i=0 or v=0
- d(i,v)=−∞ if v<0
-
d(i,v)=max(d(i−1,v),d(i−1,v−ci)+wi)
if i>1 and v>0
- d(n,T) 就是我們要的答案!
複雜度
- 狀態數: n×T
- 狀態轉移時間: O(1)
- 總共時間: n×T×O(1)=O(n×T)
Top Down
int dp[MAXN][MAXT] = {};
bool vis[MAXN][MAXT] = {};
int d(int i, int v){
if(i==0||v==0) return 0;
if(v<0) return -9999999;
if(vis[i][v]) return dp[i][v];
vis[i][v] = true;
return dp[i][v] =
max(d(i-1,v), d(i-1,v-c[i])+w[i]);
}
Bottom Up
int dp[MAXN][MAXT] = {};
for(int i=1; i<=n; ++i)
for(int v=c[i]; v<=T; ++v)
dp[i][v] =
max(dp[i-1][v], dp[i-1][v-c[i]]+w[i]);
滾動陣列
int dp[2][MAXT] = {};
for(int i=1; i<=n; ++i)
for(int v=c[i]; v<=T; ++v)
dp[i&1][v] =
max(dp[(i-1)&1][v], dp[(i-1)&1][v-c[i]]+w[i]);
int ans = dp[n&1][T];
狀態壓縮
int dp[MAXT] = {};
for(int i=1; i<=n; ++i)
for(int v=T; v>=c[i]; --v)
dp[v] = max(dp[v], dp[v-c[i]]+w[i]);
int ans = dp[T];
重要參考文獻
練習題
BasicDynamicProgramming
By jacky860226
BasicDynamicProgramming
- 589