演算法專題——樹型動態規劃
樹形DP的特點
樹形DP通常在樹種進行的DP操作,通常需要將節點的編號壓入狀態方程之中,例如dp[i]
可以表示以節點i為根節點的子樹可以得到的最大分數。
樹形DP的轉移方程通常也發生在父節點與子節點之間,詳情可以見下面例題。
還有一種樹形DP會結合dfs序進行考察,這時候狀態方程以及轉移方程又不一樣,例如下面的加分二叉樹
,狀態方程dp[i][j]
表示如果節點i到節點j形成一顆子樹,那麼可以得到的最大分數為多少。而狀態轉移方程也是針對於子樹的長度進行的。詳情見加分二叉樹
。
例題
沒有上司的舞會
題面:
分析:
dp[i][0]表示以節點i為根的子樹,且i不參加的最大快樂值,dp[i][1] 為i參加的最大快樂值。
很容易可以發現,轉移方程發生在子節點與父節點之間,見下
dp[i][1] = sum(dp[son][0]);
dp[i][0] = sum(max(dp[son][1], dp[son][0]));
加分二叉樹
題面:
分析:
中序遍歷:左中右;前序遍歷:中左右。對於1N來說,如果其根節點為k,那麼其左子樹便為1k - 1,右子樹便為k + 1n。很顯然如果要得到原樹1n的最大加分就應該要得到滿足使score(l,k-1) * score(k+1,r)+score[k]最大的根節點以及左右子樹,而score(l,k-1) 與score(k+1,r)的大小根據其根節點的位置不同,分數也不一樣,而我們要的顯然使最大分數的那一個選擇,於是我們就將原問題變成了子問題了。即要將求score(i, j)的最大分數變成求score(i, k - 1)與score(k + 1, j)的最大分數。
得到遞推方程:
dp[i][j] = max(dp[i][k - 1] * dp[k + 1][j] + val[k]) //dp[i][j]表示節點i到節點j成樹時的最大分數,val[k]表示該節點的分數
得到遞推方程之後,自然而然的可以想到兩種方法實現,一種是dfs記憶化搜尋,另一種是仿照區間動態規劃的方法解決。
記憶化搜尋就是第一題用到的方法,這裡不再贅述。而區間動態規劃的方法可以參考這篇部落格 演算法專題——區間型動態規劃,大概意思就是最外層迴圈的內容為樹的長度,從1迴圈到n即可,下面給出大致程式碼(來自第一篇題解)
for (int len = 1; len < n; ++len) { //最外層迴圈,對長度進行迴圈 for (int i = 1; i + len <= n; ++i) {//左邊界 int j = i + len; //右邊界 f[i][j] = f[i + 1][j] + f[i][i];//預設它的左子樹為空,如果有的話,這肯定不是最優解 root[i][j] = i;//預設從起點選根 for (int k = i + 1; k < j; ++k) { if (f[i][j] < f[i][k - 1] * f[k + 1][j] + f[k][k]) { f[i][j] = f[i][k - 1] * f[k + 1][j] + f[k][k]; root[i][j] = k; } } } }
有線電視網
題面:
分析:
這道題不同於我們前面提到的沒有上司的舞會或是加分二叉樹,這道題形成的樹與分數是有關的,但是要求的並不是可以得到的最大分數,而是在分數非負的情況下讓儘可能多的人看直播,此時要求的是最大人數,分數僅是限制條件,而對於這種最終要求的東西以及計算過程中的限制條件我們在dp方程中都是要體現的,於是我們可以得到狀態方程
dp[i][j] //表示以節點i為根節點的子樹,服務j個人時可以獲得的最大分數
//節點,要求的結果以及限制條件分數都在dp中體現了
那接下來,稍微動動腦筋就可以得到遞推方程了。程式碼原理可以參考分組揹包,這道題中每一個位元組點即為一個組,要做的就是一個個的遍歷組,然後進行更新。
for (int i = 0; i < fa.son(); i++) {
for(int j=fa.size(); j > 0; j--) { //fa.size()表示父親節點目前最多可以服務的客人數量
for(int k = 1; k <= son.size(); k++) { //遍歷當前子節點最多可以服務的人
if(j - k >= 0)
dp[u][j]=max(dp[u][j],dp[u][j-i]+dp[v][i]-edge[k].w);
}
}
}
騎士
題面:
分析:
雖然說是一個圖,但其實就是一顆樹多加了一條邊(一般稱這種樹為基環樹)。在原有的樹中添了一條邊便會形成環,在普通的DP中如果成環了,往往需要解環操作,這道題的解環操作稍微想想也可以想到,取環上兩個相鄰的節點,分別進行樹上DP。狀態方程和轉移方程基本可以類比前面講過的“沒有上司的舞會”,於是我們可以得到最終的答案為ans = max(dp[i][0],dp[j][0]);
其中i和j分別為上述說的環上任意兩個相鄰的節點。至於為什麼是這樣也非常好理解,因為i和j不可得兼,所以一定要其中一個不出場,當其中一個不出場時可以發現此時的圖等價於將多添的那條邊產生的影響去掉了,所以可以直接使用樹形DP的方法求解,最後便是從兩個答案(i不去或j不去)之中選了。