各类dp的总结+例题
前言
這是一篇自己對各類動態規劃和動態規劃技巧的總結,未完待續,后續會一邊刷題一邊添加內容
關于填表法和刷表法
在我們做的題中,一般填表法用得較多,但兩者各有好處
一般來說動態規劃中,狀態的轉移可以理解成圖論中的一條有向邊(u→vu \rightarrow vu→v),我們以 u 作為已求出來的狀態,v 為需要求的狀態,那么填表法便是將目光放在 v 上,去尋找已知的 u 來更新;而刷表法則是將目光放在u 上,利用已知的 u 來去更新可抵達的 狀態 v
具體來說填表法會寫成這樣 :dp[i] = dp[i - j] + w
而刷表法會寫成這樣: dp[i + j] = dp[i] + w
填表法的好處不多說,畢竟平時用得最多的便是填表法
而刷表法的好處便是:一般對于一道題中,若是在求本狀態時,尋找需要利用到的狀態比較困難時,則可以考慮用刷表法
關于空間優化
動態規劃中,真要考起動態規劃來,感覺很少卡空間,但還是說說吧
一般我碰見比較常見的空間優化的方式有兩種:
一種是狀態轉移時,本狀態的維度只用到了上一個狀態的維度,即 dp[i][x] = dp[i - 1][y] 這種形式,這樣的優化方式有很多,例如開數組時將那一維開成2的大小,又例如直接把那一維刪了
另一種是多維度狀態數組超內存的情況,一般來說這種的特點是,有一維可以利用其它幾維間接推出來,固優化方式是直接將那一維刪了,然后轉移時,那一維的信息直接利用其它維信息推出來。例題 [NOIP2010]烏龜棋 : 本題不能直接開5維度的空間
關于狀態轉移——tmp數組
為什么要總結這個?個人感覺這個真的很重要,在樹形背包的應用上有奇效
tmp數組只是我自己的叫法,具體怎么用得慢慢解釋
對于上述所說的 “本狀態的維度只用到了上一個狀態的維度” 的空間優化,有人是這么處理的
int dp[2][N]; // 聲明 for (int i = 1; i <= n; ++i) {for (int j = 1; j <= m; ++j) {dp[i & 1][j] = max(dp[i & 1][j], dp[!(i & 1)][j - x] + y);} }但如果使用tmp數組則可以這樣,有兩種方式
int dp[N], tmp[N]; // 聲明 /* 方式一 */ for (int i = 1; i <= n; ++i) {for (int j = 1; j <= m; ++j) tmp[j] = dp[j]; // 先復制,記錄沒轉移前的狀態for (int j = 1; j <= m; ++j) {dp[j] = max(dp[j], tmp[j - x] + y); // 在轉移} } /* 方式二 */ for (int i = 1; i <= n; ++i) {for (int j = 1; j <= m; ++j) tmp[j] = -inf; // 先初始化for (int j = 1; j <= m; ++j) {tmp[j] = max(tmp[j], dp[j - x] + y); // 再轉移}for (int j = 1; j <= m; ++j) dp[j] = tmp[j]; // 最后復制回dp數組里面 }使用tmp數組的好處是思路清晰,處理簡單
注意,方式一和方式二各有好處,不同題目的初始條件不同兩者的效果可能會不同
關于更新狀態
尋常來說我們更新狀態直接利用遞推便可
但有些題目最終利用到的狀態并沒有多少,但直接遞推又不好找轉移的狀態,并且直接遞推復雜度可能頂不住
固一般來說這樣的狀態轉移可以考慮記憶化搜索,類似剪枝一般在尋找狀態時只搜索自己需要用到的狀態,同時,有時狀態可能是無限的,也可能超出了數組的下標承受范圍,則可以考慮使用map存儲狀態
關于時間復雜度
一般來說,我們dp的時間復雜度可以從空間大小來推測,因為一般我們的狀態若是幾乎都要遍歷一遍的話,其復雜度的下限便是其空間的大小
關于尋找具體方案
對于這種題目,求最后的答案時可以像尋常般進行dp,而反過頭來求出答案來源的具體方案我一般是dfs往回找,時間負責度也不高
對于求解對字典序有要求的具體方案,我會反過來dp,然后正過來dfs找具體方案
當然,也看過別的求解具體方案的方式:多開一個數組記錄轉移的路線,然后像是遍歷圖一樣找路線
線性dp
線性dp——背包
對于背包真的不想多說了,刷多了自然有就感覺了,我的《背包問題》這篇博客我也總結得不少了
例題
- AcWing 2. 01背包問題 01背包入門題
- AcWing 3. 完全背包問題 完全背包入門題
- AcWing 4. 多重背包問題 I 多重背包入門題
- AcWing 5. 多重背包問題 II 多重背包進階題
- AcWing 6. 多重背包問題 III 多重背包困難題
- AcWing 7. 混合背包問題 混合背包
- AcWing 8. 二維費用的背包問題 二維背包
- AcWing 9. 分組背包問題 分組背包
- AcWing 11. 背包問題求方案數 背包問題求方案數
- AcWing 12. 背包問題求具體方案 背包問題求具體方案,可以試試上面說的求具體方案的方法
- NC23413 小A買彩票 一個簡單的背包問題
- NC14526 購物 01背包+分組背包
- NC207751 牛牛的旅游紀念品 背包問題入門題
- NC16693 [NOIP2001]裝箱問題 裸的01背包
- P1048 [NOIP2005 普及組] 采藥 裸的01背包
- NC17871 CSL分蘋果 轉換成01背包寫
- P1064 [NOIP2006 提高組] 金明的預算方案 我是當成有依賴的背包寫的,但也有人用線性方式的dp寫出來了,所以放這吧
- NC14699 隊伍配置 多維背包的入門題
- [USACO09DEC]Video Game Troubles 分組背包+01背包,轉移用兩個方程
- NC17193 簡單瞎搞題 分組背包用bitset優化
- NC16576 [NOIP2012]擺花 分組背包
- P5020 [NOIP2018 提高組] 貨幣系統 完全背包的應用
- P1877 [HAOI2012]音量調節 簡單背包問題
- P4158 [SCOI2009]粉刷匠 劃分dp + 分組背包,有點進階
- CodeForces 755F PolandBall and Gifts 多重背包,說實話有點毒瘤
- CodeForces - 864E Fire 貪心+01背包
- CodeForces - 366C 思維+01背包,有點進階
- atcoder Knapsack 2 01背包+換意dp
- atcoder Candies 多重背包,需要一些優化手段
- atcoder Tower 思維+01背包,有點思維性,多想想
- CodeForces 1637D. Yet Another Minimization Problem 打比賽時發現的,好題
其他線性dp
線性dp是最基礎的dp,其他dp可以說是他的延伸,線性dp難起來一般的難點是轉移上
線性dp考點有很多,包括空間優化,狀態轉移和定義,尋找具體方案等
多刷題才是王道
例題
-  NC20875 舔狗舔到最后一無所有 這題的轉移有點意思 
-  NC20650 可愛の星空 這題的轉移注意狀態的空間,建議使用dfs找狀態 
-  NC51216 花店櫥窗 這題感覺像是背包+數字三角形的模型,還要求具體方案 
-  NC16850 免費餡餅 加個時間軸作為狀態的一維,然后像數字三角形一樣走,反過來遍歷就好了,然后正過來找具體方案 
-  NC16856 釘子和小球 這題有點好玩,初始狀態有點意思 
-  NC16619 [NOIP2008]傳球游戲 很簡單的線性dp 
-  NC16664 [NOIP2004]合唱隊形 算LIS的入門題吧 
-  [NOIP1999] 攔截導彈 相信不少人做過這題,Dilworth定理的應用 
-  NC15553 數學考試 算劃分dp吧 
-  NC19158 失衡天平 這種轉移也有點意思,沒遇到過可能真要想挺久的 
-  NC13885 Music Problem 要用到bitset優化,想想取余的加減法對應的操作吧 
-  NC14704 美味菜肴 先貪心,后dp,最后找最優值 
-  NC207781 遷徙過程中的河流 貪心思維的dp 
-  [NOIP2008 提高組] 傳紙條 建議記憶化搜索,可以空間優化 
-  P1004 [NOIP2000 提高組] 方格取數 和傳紙條差不多 
-  P1052 [NOIP2005 提高組] 過河 空間優化,轉移找狀態有點惡心 
-  NC16590 烏龜棋 需要空間優化 
-  NC50959 To the Max 前綴和與最大連續子串的應用 
-  P1169 [ZJOI2007]棋盤制作 思維+最大全為1的正方形+單調棧的應用 
-  P2331 [SCOI2005]最大子矩陣 狀態有點難想,也有點難轉移 
-  AcWing 431. 守望者的逃離 轉移挺有意思的,記住特判條件就好了 
-  [USACO 2008 Jan S]Running 刷表法的應用 
-  P2051 [AHOI2009]中國象棋 要結合組合數學轉移 
-  atcoder Grid 2 需要組合數學和走格子的一個數學小結論 
區間dp
一種具有區間性質的dp,一般來說轉移都是從小區間轉移到大區間,固一般是先枚舉小區間再枚舉大區間的即 dp[l][r] <- dp[l + 1][r - 1]
有兩種遍歷方式可以做到
//1 比較常見的 for (int len = 1; len <= n; ++len) {for (int l = 1, r = l + len - 1; r <= n; ++l, ++r) {// 轉移} } //2 可以反向l只會找l+1轉移,r只會找r-1轉移,固可以l逆著遍歷,r順著遍歷 for (int l = n; l >= 1; --l) {for (int r = l; r <= n; ++r) {// 轉移} }兩種效果差不多
對于環形的區間dp,一般的處理方式是拆環成鏈,開兩倍的空間
例題
-  NC14701 取數游戲2 區間dp入門題 
-  NC15447 wyh的問題 區間dp題,沒什么好說的 
-  石子合并 經典 
-  凸多邊形的劃分 經典 
-  P4170 [CQOI2007]涂色 區間dp,感性轉移 
-  NC16129 小小粉刷匠 和涂色差不多,多了個限制條件 
-  NC23501 小A的回文串 拆環成鏈 
-  NC227595 跳跳跳 區間dp,也不難想 
-  NC13230 合并回文子串 如果能想到是區間dp就挺簡單的了 
-  P3147 [USACO16OPEN]262144 P 區間dp+換意dp 
-  P1005 [NOIP2007 提高組] 矩陣取數游戲 普普通通的區間dp 
-  P1040 [NOIP2003 提高組] 加分二叉樹 需要一點樹的中序和前序遍歷的知識 
-  HDU 2476 String painter 和涂色差不多,需要預處理,然后來個區間dp,最后劃分dp 
-  UVA 10617 Again Palindrome 求區間中有多少個回文子序列,需要容斥一下 
-  POJ 1159 Palindrome 需要空間優化 
-  atcoder Deque 思維+區間dp 
樹上dp
個人感覺,樹上dp和樹上背包是不一樣的,固分開來寫
樹形dp簡單的來說就是在樹上進行dp
但有些模型個人覺得還是必須要去了解的
例如
- 樹上獨立集
- 樹的最小支配集
- 樹的最小點覆蓋
- 樹的直徑
- 樹的重心 - 性質如下
- 一棵樹最少有一個重心,最多有兩個重心,若有兩個重心,則他們相鄰(即連有直接邊)
- 樹上所有點到某個點的距離和里,到重心的距離和最小;若有兩個重心,則其距離和相同
- 若以重心為根,則所有子樹的大小都不超過整棵樹的一半
- 在一棵樹上添加或刪除一個葉子節點,其重心最多平移一條邊的距離
- 兩棵樹通過連一條邊組合成新樹,則新樹重心在原來兩棵樹的重心的連線上
 
- 樹的中心
- ……
當然還有比較常見的便是換根dp和基環樹dp
對于換根dp,換根時個人覺得還是多開一個數組進行記錄需要的答案比較好。一般來說思考換根的公式是畫圖和進行未換根前的公式轉換
例題
-  Libre 10157 「一本通 5.2 例 5」皇宮看守 樹上最小支配集 
-  NC51178 沒有上司的舞會 樹上最大獨立集 
-  Libre 10156 「一本通 5.2 例 4」戰略游戲 樹上最小點覆蓋 
-  Libre 10159 「一本通 5.2 練習 2」旅游規劃 樹的直徑+具體方案 
-  POJ 1655 Balancing Act 樹的重心 
-  NC202475 樹上子鏈 有點像樹的直徑 
-  NC211219 電話網絡 樹上最小支配集 
-  NC22598 Rinne Loves Edges 直接樹上dp就行了 
-  P4268 [USACO18FEB]Directory Traversal 換根dp 
-  P2986 [USACO10MAR] Great Cow Gathering 換根dp 
-  NC51180 Accumulation Degree 換根dp 
-  NC51222 Strategic game 樹上最小點覆蓋 
樹形背包
樹形背包,顧名思義是在樹上的或者說是有依賴的背包問題
對于思考方式可以看看之前寫的《樹形背包思考方式》
一般困難點便是轉移的思考、如何定義狀態、邊和點權哪個是物品重量等
當然還有一個進階考法是利用虛樹進行優化
例題
-  AcWing 10. 有依賴的背包問題 入門題 
-  Libre 10154. 「一本通 5.2 例 2」選課 入門級別吧 
-  Libre 10153. 「一本通 5.2 例 1」二叉蘋果樹 入門級別 
-  NC20811 藍魔法師 要學會思考什么是背包模型中的物品重量 
-  P1273 有線電視網 樹形背包+換意dp 
-  P1272 重建道路 這題也不難 
-  P3177 [HAOI2015]樹上染色 注意背包模型中的物品是什么 
-  HDU6540 Neko and tree 進階題 
-  P3354 [IOI2005]Riv 河流 有點難 
狀壓dp
狀態壓縮一般指的是利用二進制進行狀態的壓縮,很少出現三進制這些
當然狀態壓縮dp常見的考點便是哈密頓通路的模型
注意一個二進制狀態尋找子集的搜索方法,其復雜度是O(3n3^n3n)的
for (int s1 = s; s1; s1 = (s1 - 1) & s) {int s2 = s ^ s1;// s1 與 s2 互為 s 的補集 }例題
-  AcWing 292. 炮兵陣地 基礎題 
-  P1896 [SCOI2005]互不侵犯 入門題 
-  NC17890 方格填色 要用快速冪優化 
-  NC210981 mixup2 混亂的奶牛 算是基礎題吧 
-  AcWing 291. 蒙德里安的夢想 狀態dp,個人覺得輪廓線的思想來轉移會比較好 
-  NC15832 Most Powerful 好好思考狀態 
-  P1879 [USACO06NOV]Corn Fields 基礎題 
-  NC16122 郊區春游 本質是求一個哈密頓最小路徑的通路 
-  NC16544 簡單環 類似求一個哈密頓回路的方案數 
-  P3118 [USACO15JAN]Moovie Mooving 思考狀態存什么 
-  P3092 [USACO13NOV]No Change 要二分優化 
-  atcoder Matching 二分圖的完美匹配方案數 
-  atcoder Grouping 你需要學會枚舉子集 
-  [CQOI2012]局部極小值 狀壓dp+容斥(難度較高) 
-  leetcode 6007. 數組的最大與和 這題有點意思,建議用狀壓做 
-  atcoder ABC213G - Connectivity 2 枚舉子集,要想到如何不重不漏地計算 
數位dp
說起數位dp,求解的過程更像是在樹上計數一樣
值得注意的是:記憶化遞歸求解簡單易懂,而且幾乎不會卡遞歸的方式
給出遞歸時的一個套路代碼(很多地方更多的是希望根據題意來)
有時候有些數位dp可能對前導0或者前面填的數對后面有影響,則可以在dfs中傳參傳多幾個標志,然后在更新res(12行)時特判就好了
當然,個人感覺核心代碼就是11~13行內部的轉移方式,一般來說,寫題的時候有點糾結狀態怎么定義時,會先寫這部分
int num[100], dp[100];int dfs(int indx, int limit, /*參數根據題意來添加*/) {if (indx == 0) {return 1;// 根據題意來返回}int &ref = dp[indx];if (!limit && ref != -1) return ref;int res = 0;int up = (limit ? num[indx] : 9);for (int i = 0; i <= up; ++i) {// 更新resdfs(indx - 1, limit && i == up);}if (!limit) ref = res;return res; }int solve(int x) {if (!x) return 1; // 根據題意來決定返回值int len = 0;while (x) {num[++len] = x % 10;x /= 10;}return dfs(len, 1); } int main() {memset(dp, - 1, sizeof dp); // 根據題意決定初始化的時機和大小// 此行省略讀入cout << solve(r) - solve(l - 1) endl; }例題
- Libre 10164. 「一本通 5.3 例 2」數字游戲 入門好題
- Libre 10166. 「一本通 5.3 練習 1」數字游戲 和上一題處理差不多
- [SCOI2009] windy 數 多少人的剛學是做的這題?
- atcoder abc161D Lunlun Number 不說應該也能想到怎么做的題
- CodeForces 204A Little Elephant and Interval 這題讓我吹爆記憶化搜的數位dp,處理前導0的形式值得練習
- CodeForces 1143B Nirvana 這題雖然可以不用數位dp做,但可以當做數位dp的練手題,可以練習一下處理前導0的形式
- And and Pair 19年南昌icpc的C題,重現的時候自己是組合數學寫出來的,但這題確實可以用數位dp寫,注意是兩個限制維度
- Sum of Log 20年上海icpc的C題,也是兩個限制維度限制的數位dp
~持續更新中……
總結
以上是生活随笔為你收集整理的各类dp的总结+例题的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 超级玛丽游戏开发五(动作音效)
- 下一篇: 计算机网络哪个学校好厦门,厦门较好的的计
