基礎DP
建國中學 游承曦
什麼是DP?
Dynamic Programming, 動態規劃
如何解決一個大問題?
把他拆成小問題再合併求出答案!
- 最佳解問題
- 組合計數
一些不重要的名詞(?
-
最佳子結構
-
重複子結構
-
無後效性
可能以後就能滲透了吧
DP的步驟
-
定義狀態
-
寫出轉移
-
打好邊界
時間複雜度通常是 \(O(\) 狀態數 \(\times\) 狀態轉移花費 \()\)
直接看例子吧OAO
ZJ d212 - 東東爬階梯
東東要來爬樓梯,他每次只會跨一階或兩階,假設樓梯共有 \(n\) 階,則他有幾種爬樓梯的做法?
這時候你可以選擇
排列組合 或 DP
顯然排組是還沒教過的東西,吧?
那就來DP吧
2. 對於東東而言,他只能從前兩階或前一階走上來
因此走到第 \(i\) 階的方法數就是走到 \(i-1\) 階和 \(i-2\) 階的方法數和
1. 定義 \(dp_i\) 為走到第 \(i\) 階樓梯的方法數
因此轉移就是 \(dp_i = dp_{i-1} + dp_{i-2}\)
3. 而最初始的狀態,走\(0\)階和走\(1\)階的走法都只有\(1\)種
所以邊界 \(dp_0 = dp_1 = 1\)
int dp(int n){
if(n == 0 || n == 1) return 1;
return dp(n-1) + dp(n-2);
}
哪裡出了問題?
這樣每次算走 \(n\) 階的答案都要遞迴 \(n\) 層
似乎效率太差,不滿足重複子結構?
因此我們要把重要的資料留下來!
int dp[maxn]={};
int fib(int n){
if(dp[n] != 0) return dp[n];
if(n == 0 || n == 1) return dp[n] = 1;
return dp[n] = fib(n-1) + fib(n-2);
}
把已經知道走 \(n\) 階的答案存下來
這樣以後要求他時就能直接得出答案
這種遞迴的dp寫法叫做 top-down
int dp[maxn];
dp[0] = dp[1] = 1;
for(int i=2 ; i<=n ; ++i){
dp[i] = dp[i-1] + dp[i-2];
}
// 直接查表 dp[k];
bottom-up 的作法
複雜度呢?
狀態數 \(\times\) 轉移花費
\(\to N(dp[0...N]) \times 1 (\)每次只要做加法\()\)
\(\to \ O(N)\) !
很棒的線性時間
btw 那題 ZJ d212
int 會爆, long long 也會爆
所以要開 long long unsigned
一大堆的練習題
才是進步的墊腳石
要開始腦力激盪了(?)
ZJ d784 - 連續元素的和
給你一個 \(n\) 個數的序列 \(a\),
找出某一段連續的區間為最大值?
\(<a_n> \ = \ -1, 2, 3, -4, 5\)
\(\max. = 2+3-4+5 = 6\)
1. 定義 \(dp_i\) 為包含 \(a_i\) 所形成的一段最大連續和
如何寫出轉移?
若 \(a_i\) 前一個最大連續和為負,就不要取
反之則加上去
2. \(dp_i = \max(dp_{i-1}, \ 0) + a_i\)
邊界?
3. 只要 \(dp_0 = 0\) 就好惹
4. 最後取 \(\max_{1\leq i \leq n}dp_i\) 就是答案!
NEOJ 141
修改一下剛剛那題
給你一個 \(n\) 個數的序列 \(a\),
可以選一些數字使其為最大,
但任兩個選的數字間至少要隔一個沒選的數?
\(<a_n> \ = \ -1, 2, 3, -4, 5\)
\(\max. = 3+5=8\)
轉換一下定義
1. 定義 \(dp_i\) 為 \(a_1 \) ... \( a_i\) 所形成的最佳解 (總和最大)
要求 \(dp_i\) 時有兩種狀況,選 \(a_i\) 或不選 \(a_i\)
- 如果選了 \(a_i\),那 \(a_{i-1}\) 就不能選,所以 \(a_i\) 就可以搭配 \(a_1\) ... \(a_{i-2}\) 所形成的最佳解
- 如果不選 \(a_i\) 那 \(a_1\) ... \(a_{i-1}\) 的最佳解就直接是 \(a_1\) ... \(a_i\) 的最佳解
所以轉移就會是兩者取較大者!
2. \(dp_i =\max(dp_{i-2}+a_i, \ dp_{i-1})\)
邊界!
3. \(dp_0 = 0, \ dp_1 = a_1\)
最後 \(dp_n\) 就會是 \(a_1\) ... \(a_n\) 的最佳解
也就是要求的答案
ZJ d378 - 最小路徑
給一個大小為 \(N\times M\) 的矩陣,
現在要從左上角走到右下角,且每次只能往右或往下走
每經過一個點,總花費就要加上該點的花費
求最小的總花費?
則最小花費為
\(0\to 1\to 5\to 1\to 1\to 0\)
\(=8\)
0 | 7 | 8 | 9 |
1 | 5 | 1 | 1 |
2 | 4 | 6 | 0 |
假設左上角為 \((1, \ 1)\),右下角為 \((n, \ m)\)
\(dp[i][j]\) 為走到 \((i, \ j)\) 時的最小花費
如果這一步不在邊界,可以從上方或左方走過來:
則 \(dp[i][j] = \min(dp[i-1][j], \ dp[i][j-1]) + c[i][j]\)
如果在邊界,就是以下兩種之一:
\(dp[i][j] = dp[i-1][j] + c[i][j]\)
\(dp[i][j] = dp[i][j-1] + c[i][j]\)
初始狀態可以放 \(dp[1][1] = c[1][1]\)
最後答案就是 \(dp[n][m]\)
空間優化
注意到每次轉移都是用前一列的資訊,
那再更前排的不是都不重要了?
所以我們可以把陣列用滾動的方式來使用!
也就是
\(dp[i \% 2][j] = \min\{dp[(i - 1) \% 2][j] + dp[i][j - 1]\} + c[i][j]\)
只需要額外 \(\mathcal{O}(2M)\) 的空間!
TIOJ 1288 - [IOI 1994] 三角旅行
如果上面那題懂了
那你就可以來寫IOI史上最水的題目(有oj的)
只是從長方形變成三角形而已
背包問題
FHVirus : 比 FFT 還難的東西
給你 \(n\) 個物品,每個物品有價值\(v_i\)和重量\(w_i\),
還有一個負重上限為 \(m\) 的背包,
問選一些物品不超過背包重量上限能獲得最大價值?
\(n \times m \leq 10^7\)
直覺
每次選價值最大的物品先放進背包直到放不下
或是先放價值與重量比值最大者
或是...?
反例
\(n = 3, \ m = 10\)
\(v:[ \ 12, 6, 7 \ ]\)
\(w:[ \ 6, 5, 5 \ ]\)
greedy 的想法在這裡是行不通的, 除非...?
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 0 | 0 | 0 | 12 | 12 | 12 | 12 | 12 |
0 | 0 | 0 | 0 | 6 | 12 | 12 | 12 | 12 | 12 |
0 | 0 | 0 | 0 | 7 | 12 | 12 | 12 | 12 | 13 |
湊成重量 1~10 的最大價值 \(\rightarrow\)
前
\(i\)
個
物
品
\(\downarrow\)
\(n = 3, \ m = 10\)
\(v_i : [ \ 12, 6, 7 \ ]\)
\(w_i : [ \ 6, 5, 5 \ ]\)
\(dp[i][j]\) 代表考慮了前 \(i\) 個物品,
重量湊成 \(j\) 的最大價值
要求第 \(i\) 個物品湊成重量 \(j\) 時,
轉移式為
\(dp[i][j] = \max \{dp[i-1][j-w_i]+v_i \mid j-w_i \geq 0 \}\)
邊界呢? 這題不用特別設
答案就是 \(\max\{dp[n][k]\}, k \in [0, m]\)
int main(){
int n, m;
int v[MAXN + 1];
int w[MAXN + 1];
cin >> n >> m;
for(int i=1 ; i<=n ; ++i)
cin >> v[i] >> w[i];
int dp[MAXN + 1][MAXM + 1]{};
for(int i=1 ; i<=n ; ++i)
for(int j=0 ; j<=m ; ++j){
if(j - w[i] >= 0){
dp[i][j] = max(dp[i-1][j], dp[i-1][j - w[i]] + v[i]);
}
else{
dp[i][j] = dp[i-1][j];
}
}
int ans = 0;
for(int i=1 ; i<=n ; ++i)
for(int j=1 ; j<=m ; ++j)
ans = max(ans, dp[i][j]);
cout << ans << '\n';
}
滾動DP
可以發現轉移時都是從 \(dp[i-1]\) 排轉移過來,
因此一樣可以只開 \(2 \times M\) 大小的陣列
每次交換兩排來當作轉移來源
比較特別的是對於背包問題可以壓成一個維度
只是要逆序枚舉
逆序枚舉
無限背包
有限背包
區間DP
LCS
Longest Common Subsequence
LIS
Longest Increasing Subsequence
位元DP
e.g. 旅行推銷員問題
基礎DP
By youou
基礎DP
- 181