記錄關於DP的歷程,隨着刷題而更新。
個人向,僅自己回顧用,若是有講不清楚的地方歡迎回復私信交流。
一般情況下,動態規劃的解題步驟是:
第一步:根據原問題和子問題來確定狀態(dp數組表示什麼東西)
第二步:根據狀態確定狀態轉移方程(怎樣求解dp數組 遞推?dfs?)
第三步:確定要不要優化和編程實現方式 (單調隊列?線段樹?)
先學習簡單的線性DP,其實關於線性DP的話,不外乎這幾種。
1. LIS(最長上升子序列)
其實LIS的話,是dp入門必不可少的題目,正常時間算法時間複雜度 n^2,可用二分或樹狀數組優化達到 nlogn,用 dp[i] 維護 i 位置的最小值,最後dp數組的長度即最長上升子序列的長度,但是具體的過程和內容卻無法求。
len=1;
dp[1]=a[1];
for(int i=2;i<=n;i++){
if(dp[len]<a[i]) dp[++len]=a[i];
else{
ll k=lower_bound(dp+1,dp+len+1,a[i])-dp;
dp[k]=a[i];
}
}
2. LCS(最長公共子序列)
特殊的,假設序列a和b是1-n的排列組合,那麼可以離散化a序列,那麼求出b的最長上升子序列就是a與b的最長公共子序列。
tip: 因爲最長公共子序列是按位向後比對的,所以a序列每個元素在b序列中的位置如果遞增,就說明b中的這個數在a中的這個數整體位置偏後,可以考慮納入LCS——那麼就可以轉變成nlogn求用來記錄新的位置的離散化後的LIS。
3. 揹包問題
揹包問題就是以01揹包爲基礎進行擴展的一系列問題。
當求最優/最大利益時,假設 M 爲物品組數,T 爲最大容量,a[i] 爲每一件物品的體積,b[i] 爲每一件物品的價值,c[i] 爲消耗的容量不大於i的情況下所獲得的最大收益,則有 :
// 1. 逆序 01 揹包
for(int i=1;i<=M;i++)
for(int j=T;j>=a[i];j--)
c[j]=max(c[j],c[j-a[i]]+b[i]);
//2. 正序 完全 揹包
for(int i=1;i<=M;i++)
for(int j=a[i];j<=T;j++)
c[j]=max(c[j],c[j-a[i]]+b[i]);
而其他的情況多重揹包不過是多了一個把物品拆分/合併的過程,而分組揹包不過是多了一個決策過程,經過這些的猜想,不難得出揹包的dp模板:
- 第一層 for 枚舉物品的組數
- 第二層 for 枚舉物品的容量
- 第三層 for 進行決策
而當遇到求方案數時,也是可以由揹包得出經驗,不外乎個數有限與無限的區別,那麼其實就是01揹包/完全揹包:
// 1. 逆序 01 揹包
for(int i=1;i<=M;i++)
for(int j=T;j>=a[i];j--)
c[j]+=c[j-b[i]];
//2. 逆序 完全 揹包
for(int i=1;i<=M;i++)
for(int j=a[i];j<=T;j++)
c[j]+=c[j-b[i]];
可以發現,不過是狀態轉移的方程改變了而已。
4. dirworth定理
求最長上升序列的長度等於求最長不上升序列的劃分個數。
5. 其他問題總結
-
迴文串:dp[i][j] 爲字符串 的第 i 個字符到第 j 個字符的最長迴文子序列長度。
特殊的:
一般的: -
題目:多個串的匹配,考慮 爲s1的前i個字符與s2的前j個字符。
-
題目:n個箱子選前k小的價值和,例如 爲前i個箱子的價值爲k的方案。
-
題目:對於挑選方案可以按照揹包思想,根據題目劃分爲01揹包或者完全揹包,再考慮是否是分組揹包(有決策層)。
-
題目:對於數列型題目,可以考慮類似於LIS或者LCS來思考,以表示以當前元素結尾的狀態值,若是元素範圍少,可考慮爲 以 結尾且和爲 或者差爲 等等狀態,把第二維作爲對於給出條件的一種枚舉。
以上,其他內容等遇到再補充。