Dynamic Programming, DP
作者:sa072686 (sa072686@gmail.com)
符合條件有幾種不同情形、幾種走法、…
0
n
0
n
可知:抵達第 x = n-1 階後,再爬 1 階就到第 n 階
設抵達第 n-1 階有 100 種方法
每種各能提供 1 種抵達第 n 階的走法 → 總共 100x1 種
可知:抵達第 x = n-2 階後,再爬 2 階就到第 n 階
設抵達第 n-2 階有 50 種方法
每種各能提供 1 種抵達第 n 階的走法 → 總共 50x1 種
0
n
★最後一步爬 1 階
★最後一步爬 2 階
?? 1
最後一步
抵達第 n-1 階的任何方法
抵達第 n 階
的一種方法
+ 抵達第 n-2 階方法數 x 1
設函數 f( i ) = 抵達第 i 階的方法數
達成目標所有方法中最佳花費、最小步驟、…
0
n
4
2
7
3
故 c 不是最佳解,得證「若當前不是最佳,未來也必不是」
重複問題不計算,故與一般遞迴不全相同
int stair( int n )
{
if (n <= 1)
return 1;
return stair( n-1 ) + stair( n-2 );
}
※ stair 可自由替換成好理解的名字
※ 和 f() 成長一樣快,常數更大
直接查表 O(1)
直接查表 O(1)
int stair( int n )
{
if (used[n])
return dp[n];
used[n] = true;
if (n <= 1)
dp[n] = 1;
else
dp[n] = stair( n-1 ) + stair( n-2 );
return dp[n];
}
☆Top-down★
int stair( int n )
{
if (n <= 1)
return 1;
return stair( n-1 ) + stair( n-2 );
}
int stair( int n )
{
if (used[n])
return dp[n];
used[n] = true;
if (n <= 1)
dp[n] = 1;
else
dp[n] = stair( n-1 ) + stair( n-2 );
return dp[n];
}
※ code 和前面相同,純對照用
dp[0] = 1;
dp[1] = 1;
for (i=2; i<=n; i++)
dp[i] = dp[ i-1 ] + dp[ i-2 ];
※ 兩種都要最低限度會寫會用,才有資格偏食
(個人強烈推薦 Bottom-up 但絕不能只練一種)
DP 節省的計算量從何而來?
修改狀態來拆散不該被壓縮在一起的方法們
答案在下一面,此頁防雷
給 n 種硬幣面額,求湊出 k 的方法數
2 + 5 和 5 + 2 算不同方法
考慮總和為 k 時,面額若有 {2, 3, 5}
最後使用 2 時,剩下為 k-2
最後使用 3 時,剩下為 k-3
最後使用 5 時,剩下為 k-5
則 k-2, k-3, k-5 每種做法
各可以提供 1 種方式達成總和 k
由於順序不同視作不同
其實等價於爬樓梯問題
設狀態 dp(i) 為總和 i 之方法數
Ck 為第 k 種面額
給地圖,有障礙物
每步只能往右或往下
求從左上走到右下的方法數
每步往下或往右
故每格必從上或左兩方向過來
抵達左、上兩格的每種走法
都能提供 1 種走法
設 dp(i, j) 為抵達座標 (i, j) 之方法數
給 n 個路段用 3 種不同跑法所需時間
每種跑法至少要跑 1 段路
每段路必須使用相同跑法跑完
從跑法 i 只能切換至跑法 i+1
求最小所需時間
目前所在路段影響接下來要跑多少
目前跑法影響下一段路能用哪些跑法
因此這些影響未來發展的必須增維
考慮第 i 段路以第 k 種跑法跑完
那麼它第 i-1 段必是用 k or k-1 種跑法跑完
從所有符合的情況選最佳
即為當前最佳
設 dp(i, j) 為用第 j 種跑法跑完路段 1~i
所需最小時間
其中 Ti,j 為第 i 路段以 j 跑法跑完的時間
在 n 天每天從 3 種活動選 1 執行
給每一天每種活動的快樂值
不能連續 2 天進行同種活動
求這 n 天最大快樂值總和
今天是第幾天影響每種活動快樂值和天數
今天的活動影響明天能做的事
以上皆影響未來發展
考慮今天做第 j 種活動
那麼前一天必須做 (j+1)%3, (j+2)%3 種活動
使活動不連續
從不連續中取最佳幸福值
為當前最佳
設 dp(i, j) 為到第 i 天為止
最後一天進行第 j 種活動
的最大幸福值
Hi,j 為第 i 天做 j 活動的幸福值
給 n 數,每數可選要加或減
求最終算出的值為 k 的倍數
n <= 10000
k <= 100
考慮最終為 k 倍數
設最後一數為 5
則前面總和需為 k*x - 5 或 k*x + 5
也就是說,設前一步總和為 s,則
(s + 5) % k == 0
(s - 5) % k == 0
至少其中之一必須可達成
故子問題須湊出 %k 下
特定餘數能否達成
設 dp(i, j) 為到第 i 數時
總和 % k 為 j 是否可能達成
給長度 n 兩序列 A, B
每元素由 0 到 k-1 構成
定義 H(i, j) 為兩序列 i, j
有多少個位置的元素值不同
給整數 d
求存在多少種相異序列 C
C 每元素由 0 到 k-1 構成
且滿足 H(A, C) + H(B, C) = d
考慮 C[n] 的所有情況
距離和必須為 d
故視 C[n] 前 n-1 個字可能距離必須為
d, d-1, d-2 其中一種
設 dp(i, j) 為 C[1] 到 C[i] 這段
和 A,B 距離總和為 j 的方法數
記錄每種狀態從誰轉移而來
由終點狀態逆推即可
n 天每天買 A,B 福袋其中一種
給每天的 A,B 福袋價格
求總花費恰為 S 元的買法
每天買什麼不影響,只有總價格影響未來
第 n 天總和恰為 S 元
表示第 n-1 天必須達成
其中一種金額
設 dp(i, j) 為到第 i 天買了總和 j 元
是否可能達成
記錄 F(i, j) = A or B
若 dp(i-1, j-Ai) 可達成則記 A
若 dp(i-1, j-Bi) 可達成則記 B
若皆可達成,則視題目要求決定記誰
最後從終點狀態照 F 的記錄回推即可
若終點 F(n, S) 為 A
則下一步回頭查 F(n-1, S-A[n])
否則回頭查 F(n-1, S-B[n])
依此類推,遞迴解決
由於終點回推時是先推得最後一步
同時是字典序中最不重要的一步
故字典序時可倒序計算
將原終點當起點,原起點當終點
回推時的最後一步
即為原第一步
大原則:轉移可能可壓,狀態數則不行
故多種可行狀態時
狀態數越少,可能性越大
有 n 個小孩,第 i 小孩要發 0~Ai 顆糖
你有 K 糖要發完,求滿足條件有幾種發法?
n <= 100
K <= 10000
考慮發完 K 糖時
第 n 個小孩可能取得 0 到 A[n] 顆
也就是說,前 n-1 小孩發掉的糖數
必須為 K-0 到 K-A[n]
所以糖數重要,找到子問題描述
設 dp(i, j) 為到第 i 小孩
發掉恰 j 糖的方法數
狀態數 n * k
轉移 k
最差 100 x 10000^2 = 10^10 …
考慮 dp(i, j) 計算上需要
dp(i-1, j)
dp(i-1, j-1)
dp(i-1, j-2)
…
可視為 dp[i-1] 裡的區間和
設 pfx(i, j) 為 dp(i, 0) + ... + dp(i, j)
綜合以上,整理遞迴式為
則 dp, pfx 轉移均為 O(1)
複雜度 O(nk)
最差為 100 x 10000 = 10^6 合理
給 n, x, y
求長度 n 的 0/1 序列滿足
連續的 0/1 數量不超過 x
連續相同的區段數恰為 y
考慮第 n 元素可為 0 或 1
區段數必須恰為 y
故
若第 n-1 元素與 n 相異
則區段數增加 1
前面必須恰為 y-1 段
若第 n-1 元素與 n 相同
則屬同一區段,連續相同字數 + 1
故子問題必須同時注意區段數和連續相同數
設 dp(i, j, k) 為長度 i 時
區段數恰為 j 最後一段連續字數 k
的方法數
考慮最後一個區段,而非最後一個元素
則窮舉最後區段字數為 t = 1 到 x
前面的 n-t 元素則需湊足 y-1 區段
設 dp(i, j) 為長度 i 區段數恰為 j 的方法數
由於 k 連續,故 i - k 連續
可套用前綴和將轉移壓至 O(1)