【動態規划雜記】狀態+轉移
核心:划分階段-狀態表示-狀態轉移方程。
復雜度:狀態數O(n^t),轉移O(n^e),則稱為tD/eD問題。
1.最優化問題和方案數問題常考慮DP,特定數問題不考慮DP。
2.斷層思想:划分狀態,從計算過的狀態去答案,這就是無后效性。(eg.對於搜索樹按高度划分狀態)
3.記錄思想:通過記錄所有狀態和狀態轉移達到最終狀態。(有一種方式是把答案的要素都列入狀態,然后考慮轉移)
4.DP有三種形式:傳統式,刷表法,記憶化,選擇形式一般由方便程度決定。
5.DP可以和貪心結合來划分階段,幫助DP解決最優性未知的問題。
6.為了轉移,需要使狀態表示方便轉移(例如在狀壓基礎上把最后兩個點提出來單獨放着,因為后面要用)。
6.奇怪的DP若感覺有依賴關系,要么考慮一動一不動,要么把狀態表示出來。
7.郵局問題(x軸有n個村庄,選m個村建郵局,求所有村到郵局距離和最小):f[i][j]表示i村選j郵,枚舉一段村屬於一個郵局來轉移,郵局建在這段村中點。這是一種典型的放縮,雖然算出來不一定是當前郵局分配下的正確答案,但是正確答案能被算到,不正確答案不會更優。
8.最長上升子序列:偏序 O(n log n)。(上帝選人:二分圖不交叉最大匹配,一維排序一維LIS)
9.DAG上的動態規划:令f[i]為以i為起點的最長路——記憶化搜索
令f[i]為以i為終點的最長路——拓撲排序+DP(刷表)
10.單調隊列優化:單調隊列的作用是強制使h(i)和i同時具有單調性,從而可以同時處理坐標和最值相關的事情。
最常見的應用就是處理移動區間最值。
還有一個應用:對於條件ai>=bj找坐標最大,維護bj的單調隊列。如果ai有單調性就可以直接找隊頭,否則二分單調隊列。
收集了一個系統的教程:[dp優化]個人對dp優化的理解
11.幾個套路:
①狀態與值的互化
②和貪心的結合
③轉化為矩陣
【斜率優化】
參考:斜率優化DP(文中有些小錯誤,維護的是下凸包)
★例題:【BZOJ】1597 [Usaco2008 Mar]土地購買
斜率優化題目的通法:
1.列式:列出決策優劣比較式(i階段,j<k且k更優時),形如(y[j]-y[k])/(x[j]-x[k])<k[i],當k[]和x[]同時具有單調性時可以進行斜率優化,定義更滿足比較式為優,維護從最優到最劣的決策隊列。
2.決策:選取隊頭兩個決策d(head,head+1),若d滿足優劣式說明k更優,刪除隊頭j,繼續比較;若d不滿足優劣式說明j更優,則j為當前最優決策。
3.入隊:選取隊尾兩個決策d1(tail-1,tail)和當先決策與隊尾決策的d2(tail,i),若d2優於d1則刪除隊尾,繼續比較,直到d2劣於d1就把i入隊。
解釋:
1.對於每個決策點是二維坐標系中的一點,比較式為"<"時,只有構成下凸包的點有效(">"為上凸包)。
2.對於給定斜率k[i],需要找到凸包上斜率最接近的一條直線,恰好前一條線后面更優,后一條線前面更優,從而得到最優決策點。
3.當k[]和x[]同時單調時可以直接斜率優化,否則用CDQ分治維護。
【分治決策優化】解決決策單調性問題
參考:《淺談決策單調性動態規划的線性解法》馮哲 2017集訓隊論文
條件:決策具有單調性。
復雜度O(n log n)
令h[i]為f[i]的決策來源。
對於h[n/2],有[1...n/2]從[1...h[n/2]]決策。
有[n/2...n]從[h[n/2]...n]決策

dp[i][j] 前i個數字,分成j段,最小平方和 f[0]=0; void solve(int l,int r,int L,int R){ if(l>r||L>R)return; int mid=l+r>>1; int where,value=INF; for(int i=L;i<=R&&i<mid;i++){ if(g[i]+(sum[mid]-sum[i])*(sum[mid]-sum[i])<value){ value=g[i]+(sum[mid]-sum[i])*(sum[mid]-sum[i]); where=i; } } f[mid]=value;//mid 從 where 決策 solve(l,mid-1,L,where);solve(mid+1,r,where,R); } for(int j=1;j<=k;j++){ memcpy(g,f,sizeof(f)); memset(f,0x3f,sizeof(f)); solve(1,n,1,n); /*for(int i=1;i<=n;i++) for(int x=1;x<i;x++){ f[i]=min(f[i],g[x]+(sum[i]-sum[x])*(sum[i]-sum[x])); }*/ } k*n*n k*n*logn
分治決策優化解決決策單調性問題十分套路化而且簡便好寫。
在此之前解決決策單調性問題的方法:《用單調性優化動態規划》,可以忽略。
【背包DP】取和不取的問題可以考慮轉成背包。
<01背包>
$$f_{i,j}=max\{f_{i-1,j},f_{i-1,j-w_i}+c_i\}$$
for i:=1 to n do
for x:=m downto w[i] do {不能寫成for x:=w[i] to m}
if f[x-w[i]]+c[i]>f[x] then {f[x]表示重量不超過x的最大價值}
f[x]:=f[x-w[i]]+c[i];
<完全背包/無限背包>
$$f_{i,j}=max\{f_{i-1,j},f_{i,j-w_i}+c_i\}$$
for i:=1 to n do {f[x]表示重量不超過x公斤的最大價值}
for x:=w[i] to m do {不能象0/1背包一樣用反向}
if f[x-w[i]]+u[i]>f[x]
then f[x]:=f[x-w[i]]+u[i];
<有限背包>【HDU】2191 多重背包問題 二進制分組(一直減2^(k++)直到不能減的余數當作最后一個)
<二維費用背包>相當於兩個容量,多加一維就行。物體總個數限制也屬於此類。
加一維限制條件來滿足原來的經典轉移而已。
<分組背包>最后枚舉同組物件來限制一組一件,即對於f[v]進行全組決策完再轉移。
<Bitset優化存在性背包>f[i]表示是否存在重量為i的方案。

for(int i=1;i<=n;i++){ for(int j=m;j>=a[i];j--){ f[j]|=f[j-a[i]]; } } ////////// for(int i=1;i<=n;i++){ f=f|(f<<a[i]); }
【區間DP】
按區間長度從小到大枚舉,通過轉移體現最優子結構。
回文串擁有很明顯的區間子結構特征,當i+1 > j-1時也是有意義的,空串也是一個回文串

for i:=1 to n do f[i,i]:=0;{初始化} for p:=1 to n-1 do //合並的堆數p: 階段 for i:=1 to n-p do //枚舉狀態: begin j:=i+p; f[i,j]:=maxlongint; for k:=i to j-1 do {枚舉決策} f[i,j]:=min(f[i,j],f[i,k]+f[k+1,j]); f[i,j]:=f[i,j]+s[j]-s[i-1]; end;
【樹型DP】
<轉二叉樹>左孩子右兄弟表示法

read(i,j);//i:父;j:子 if left[i]=0 then left[i]:=j else begin right[j]:=left[i]; left[i]:=j; end;
<1>在二叉樹進行DP
狀態轉移方程模型:f[i,j]:=f[LC,k]+a[i]+f[RC,j-k](f[i,j]表示以i為根的子樹選取j個特殊節點的值)
<2>樹的經典問題
1)樹的直徑:記錄最深和次深。
有向樹的直徑:f[i]表示往上走的最長鏈,g[i]表示向下走的最長鏈,經過i的最長鏈等於f[i]+g[i]。
(紫書的題之所以采用二分答案,是因為局部最優≠全局最優,我們是要讓最長鏈(即所有鏈)嚴格小於一個值,不是讓某一段鏈盡可能小。)
2)樹上所有點的最遠點:
<1>第一次treedp,得出f[i]表示i為根的子樹的最深葉子路徑,g[i]表示次深葉子路徑。
<2>第二次treedp,對於節點x,傳下來s表示向上的最長路,所以s[x]=max(s,f[x])。
遍歷x兒子son[x]時s的傳遞:若f[son[x]]+1=f[x]則s=g[x]+2否則s=f[x]+2。
套路:當需要統計節點向上信息時,第一遍先統計向下信息,並且每個節點集成兒子的信息之和,第二遍將傳下來的信息和(節點信息減去兒子信息)傳給兒子作為向上信息。
3)樹的重心:【算法】樹
<3>在多叉樹上進行DP
當題目需要根據取或不取進行決策時,就需要在數組上多加一維01表示是否取根。
狀態轉移方程模型:f[i,j]:=f[son[i,1],j1]+f[son[i,2],j2]+...+f[son[i,x],jx](j1+j2+..+jx=j)
分配j的方法是背包:對於節點i,將一個兒子的信息與節點合並后,再轉而處理下一個兒子,當信息與節點數有關時(如特殊節點數),枚舉根信息上限(之前已合並了的子樹結點總數+當前子樹結點數,記為now_size)和當前子樹信息上限(當前子樹結點數)。
for(son[x])
for(i=0...min(now_size,maxm))
for(int j=0...min(size[son[i]],maxm))
f[x][i]=min(f[x][i],f[x][i-j]+f[son[i]][j])。
背包千萬不要枚舉滿,當信息與結點數有關時按子樹結點數枚舉,總復雜度攤下來是O(n2)。
樹型DP幾乎都是DFS。
【數位DP】專題鏈接
【插頭DP】
講解:【Ural】1519. Formula 1 插頭DP 哈密頓回路問題(單回路問題必須在最后一有效格閉合,最后一格取答案)
在特殊的題目,只要改變狀態表示就可以記錄很多信息,從而滿足題目的要求。
例題:
1.【BZOJ】2331: [SCOI2011]地板 插頭DP 特殊覆蓋問題
2.【BZOJ】2310: ParkII 插頭DP 簡單路徑問題(增加一個狀態位記錄有0~2個獨立插頭)
下面例題和內容參考kuangbinの博客:
3.FZU 1977 Pandora adventure:此題也是單回路數問題。但是格子有了三種:障礙格子,必走格子和選擇性經過格子。
題解:單回路問題的特點是必須在最后一格閉合,此題最后一格不確定,所以增加一個狀態位來記錄是否形成回路。
4.POJ 3133 Manhattan Wiring:格子中有兩個2,兩個3.求把兩個2連起來,兩個3連起來。求經過總的格子數的總和減2. 兩條路徑不能交叉。有障礙格子。非障礙格子最多經過一次。
題解:插頭記錄三種狀態:沒有插頭、2號插頭、3號插頭。
指定起點和終點的問題,只要在起點和終點處特殊處理獨立插頭的問題即可。
5.HDU 4285 circuits:求K個回路的方案數。而且不能是環套環。
題解:增加一個狀態位來記錄形成的回路個數。在第x列閉合一個回路時,如果左邊的插頭數(<x-1)是奇數就是無效狀態。
這是因為每條回路一定有偶數個插頭扎在輪廓線上。即使有環套環套環,這種狀態也會在閉合第二外層的環時被判定為無效。
6.Uva10572 Black&&White:給定n*m的棋盤,有些格已經染了黑色或白色,要求給所有格染色使得黑色和白色各自連通且不存在2*2的同色矩形。
題解:判斷2*2只需要多記錄一位狀態,主要解決黑白色各自連通的問題。枚舉過第x格時,第x格上方的方格馬上會退出輪廓線表示范圍,如果輪廓線中不存在它的連通塊,這種顏色以后就不能再出現了。出現這種情況是直接判斷剩下的方格是否成立計算答案即可。
之所以需要這么做,是因為普通路徑問題有插頭的存在,不可能丟失路徑。但當跳過第x格時,第x格上方的方格就和狀態無關了。
還有一個交叉狀態優化:如果獨立連通塊abcd(不嵌套),ac一色,bd另一色,則無解。
留坑:BZOJ1494(論文第四部分思想)+ZOJ3256(kuangbin的矩陣加速題目)
【狀壓DP】
1.預處理:如果過程中都是重復的轉移,預處理兩個狀態之間的轉移是否合法。
例題:【BZOJ】1087: [SCOI2005]互不侵犯King 預處理上下兩行的狀態是否合法。
例題:【BZOJ】2004: [Hnoi2010]Bus 公交線路 狀壓DP+矩陣快速冪 每次轉移都是調用兩個狀態判合法,直接預處理后可以矩陣快速冪加速。
狀態的設置:觀察轉移過程的需要以及數據的范圍。
2.轉移方式:
(1)遞推法,從前面取答案
(2)刷表法,加后面的答案。還可以把有加的才加入隊列,簡化狀態數(相當於廣搜BFS)。
(3)記憶化搜索,類似刷表法。
3.二進制操作技巧(分清 ”或|“ 和 ”與&“ 的區別,善用lowbit(x)取最低位的1,善用(x+1)和(x-1))
(1)單個位變0:先”或“成1,再減掉。
(2)枚舉所有集合的子集:for(int s=S;s;s=(s-1)&S)
(3)統計1的個數:不斷lowbit(x)后減掉。
(4)最右的連續0/1或第一個0/1:參考狀壓DP初探·總結,都是利用x+1影響最右連續1,x-1影響最右連續0來實現的。
4.例題:
(1)鋪地磚:每層影響下一層的狀態通過本層的鋪地磚來得到,這個鋪地磚的過程通過dfs整行來實現比較方便,參考:鋪地磚|狀壓DP練習。
(2)TSP問題:【CODEVS】2800 送外賣
-------------------------------------------------下面待整理-----------------------------------------------------------
(3)下面筆記比較亂,不舍得刪,想看就看吧。
UVA 10817 m教師n求職者給出每人工資c課程表,求最少支付使每節課至少兩個老師教。
因為至少兩個老師,S0(未教,不用記憶化,s1和s2確定時s0就唯一對應了),S1(一個老師),S2(兩個老師)。
也可以壓成一個狀態二進制16位或者四(三)進制8位,只不過操作稍顯繁瑣而狀態數實際上是一樣的。
d[i][S1][S2]表示已經考慮了前i個人取舍的最小花費(注意是考慮了,不是只考慮)。
d(i,S1,S2)=min{d(i+1,S1',S2')+c[i],d(i+1,S1,S2)}對應從取和不取(i≥m)轉移過來。
為什么是i+1?若i從i-1轉移,也就是若取i則從缺了i的狀態狀態轉移過來,狀態處理將十分不便,
方便地在二進制中體現i的取舍的方法顯然是決策取1或取0然后把這個結果送給i+1去繼續決策。
可以想象出狀態轉移是一顆從(0,0,0)發散出去最終到達n+m的樹,為了方便記憶化,用d[i][S1][S2]記錄對應子樹的值,令d[i][S1][S2]表示前i決策完畢后還需要支付給后面的n+m-i個老師的最小工資最合適。
這也體現了記憶化搜索的本質是記錄下搜索樹的子樹值在子樹根節點上。
這種有點01背包性質(取或不取)的狀壓DP,一般為了方便處理都會采用記憶化搜索,並且i+1向i轉移。
不過也可以采用刷表法,將影響到的下層狀態放入隊列。
<5>UVA1252狀態設計也十分巧妙,s表示已詢問,a表示已確認具備,轉移中取最大值,通過預處理邊界條件降低復雜度。
<6>UVA1412不必為復雜的描述困擾,實際上都是熟悉的算法。
插頭DP的優越性在於:對於一些問題來說(尤指棋盤類問題),一行的狀態有許多是前綴相同的,那么決策情況也相同,這時輪廓線就可以把相當的一起算,大大減少狀態轉移復雜度。
所有其實很多題目都可以用插頭DP加速,包括擺磚、騎士共存等棋盤問題。
插頭DP一般只記一行,但不得已也可以記兩行(騎士共存)等,如果可以傳承標記的話也可以壓成一行(但是狀態數復雜度是一樣的!)。
【未來決策DP】
——論文《對一類動態規划問題的研究》筆記
未來決策DP:★將當前決策對未來的影響都在現在計算,然后未來只要按正常決策計算就可以了
1.第一類問題,考慮代價與時間的一致性,將本來的時間維算成代價(枚舉總收益),省略時間維。
當然可以把當前收益和未來代價一起算。
核心是當前決策對未來的影響都在現在計算,然后未來只要按正常決策計算就可以了。
2.題意:給定1~n的排列,要求排序,每次操作可以將一個數從位置i移動到位置j,代價為i+j,求總代價最小。
結論:按編號從大到小處理,對於每個x,有1.移動到x+1前一個位置,2.不移動,x和x+1之間的數移動到x之前。
采用一個重要操作:將移動到x+1前一個位置視為移動到x+1的位置,簡化運算!
令f[i][j]表示第i個數移動到位置j的最小代價。pos[x]表示數x的原位置。
為了轉移時計算x的新位置,采用相對轉絕對的方法。因為<x的數字均為處理,假設>x的數都在后面,則
c(p,x)表示之前位置在p的x至現在的新位置(代價),等於1~p中小於x的數字個數+1。
枚舉x+1的位置p2,f[x][p2]=f[x+1][p2]+c(pos[x],x)+c(p2,x+1)-1。x和x+1位置前后無關。
再處理x不動的情況,k=pos[x]+1~p2-1的數字未來一定要多移動x-s[k]的代價。(s[k]表示原序列k位置的數字,x在前則x~s[k]-1都在前)
f[i][pos[i]]=min{f[i+1][p2]+∑(x-s[k]),x>s[k]}
和第一題思想一致,將當前決策對未來的影響在當下計算,未來算的時候按正常決策計算。