1. 程式人生 > 其它 >學習筆記——樹形DP

學習筆記——樹形DP

前言

學完樹形$DP$,$NOIP$會考的所有$DP$我們就都學完了,所以讓我們一鼓作氣,開始樹形$DP$的學習之旅吧!

一、樹形$DP$的基本概念

顧名思義,樹形$DP$就是在樹這種資料結構上進行$DP$(這不是廢話嗎?),通過有限次遍歷樹,記錄相關資訊,以求解問題。因為樹形$DP$是建立在樹上的,而樹中的父子關係本身就是一個遞迴結構(滿足子問題重疊性),所以自然而然地就衍生出了兩種$DP$順序:

1.葉子節點 $\Rightarrow $ 根節點:就是將葉子節點的資訊向上傳到父親節點,在父親節點處進行資訊的整合。最後由根節點記錄的資訊得到最優解。

例如本題可以將下屬來不來得到的權值作為資訊傳遞到上級

(題解)(我的程式碼)

2.根節點 $\Rightarrow $ 葉子節點:取所有點作為一次根節點進行求值,此時父節點得到整棵樹的資訊,只需要去除這個兒子節點$DP$值的影響,然後再轉移給這個兒子。(此做法一般不用,但有時可能有奇效)

樹形$DP$的順序:一般按照${\color{Red}\colorbox{White}{後續遍歷}}$的順序(先處理兒子節點再處理當前節點(這樣才符合$DP$的三個基本條件))

實現方式:樹形$DP$一般用${\color{Red}\colorbox{White}{記憶化搜尋}}$實現(既然是記搜當然用的是遞迴了)(類似於樹剖的第一個dfs)

時間複雜度:一般的樹形$DP$的時間複雜度是$O(N)$(因為每一個節點只會被遍歷一次

),如果有附加維$M$,則複雜度為$O(N\times M)$。

二、樹形$DP$的幾種型別

1.揹包類樹形$DP$

既然我們前面將揹包和樹形$DP$聯絡在一起學了,那我們就先從我們熟悉的揹包類入手吧!(這樣能增加我們的信心,減少我們對樹形$DP$的畏懼心理)

1.先來一道入門題(樹上01揹包)(題解)(我的程式碼)

通過本題,我們就可以大致瞭解樹形$DP$的思路--從根節點出發不斷向子樹進行搜尋,再將子樹的資訊合併到該節點上。(注意:本題要考慮只選該節點與子樹連線的這一條邊的情況)

2.經典的樹上01揹包(題解)(我的程式碼)

本題我們又學會了一種思想--建虛點。因為本題是在森林中選一定的節點(選完父節點才可以選子節點),但我們又不可以只由一棵樹得到最優解,所以我們可以考慮建一個虛點($0$),令它向所有樹的根節點連一條邊,接著只要對以虛點為根節點的子樹進行樹形$DP$求出最優解

即可。

3.細節較多的樹上01揹包(題解)(我的程式碼)

通過本題,我們要學會在樹形$DP$時注意細節--該節點與它兒子節點相連的邊到底可不可取,以後做樹形$DP$題推導狀態轉移方程時要仔細考慮這個問題,才能更正確的做出樹形$DP$題。

4.狀態轉移方程含義比較新奇的題(內層迴圈順序講解清楚的題解)(我的程式碼)

本題告訴我們:

(1)樹形$DP$的狀態轉移方程記錄的可以不只是一個最優解,還可以記錄該狀態對最終答案的貢獻

(2)我們可以通過建立雙向邊的方式將一棵樹強行轉化為以1號節點為根節點的樹,然後通過遍歷順序重新確立父子節點

(3)我們要注意該狀態有沒有被更新過,可以通過思考將一些不可能更新答案的狀態排除(本題中還是初始值-1的狀態),加快程式的執行效率。

5.(上一道搜尋題(大霧)(講的很詳細的題解)(我的程式碼)

先吐槽一下:普及$DP+IOI$輸入$=$提高難度

好了好了,來總結一下本題的收穫

(1)聯想已學:遇到另類的輸入時,可以聯想一下我們已學演算法中有沒有類似的結構。例如本題可以用類似線段樹的建樹方式來解決輸入問題。

(2)根據題意合理運用填表法和刷表法

還是先簡單解釋一下這兩個詞吧!

填表法 :就是一般的動態規劃,當前點的狀態,可以直接用狀態方程,根據之前點的狀態推匯出來。

刷表法:由當前點的狀態,更新其他點的狀態。

刷表法需要注意:只用於每個狀態所依賴的狀態對它的影響相互獨立

這麼說大家是不是還是一頭霧水,舉個例子:如果狀態$A$對狀態$B$有影響,狀態$C$也對狀態$B$有影響,當狀態$A$的影響和狀態$C$的影響相互不影響,就可以運用刷表法。

本題中就記錄到達該走廊末尾需要花費的時間(設為$tim$),令$j$表示分配給左兒子的時間,$k$表示分配給右兒子的時間,然後更新以$u$為根節點的樹花$tim+j+k$秒可以取得的最大價值。這樣可以極大地減少思考難度。

繼續挖坑 (挖坑太多後面會不會填不完啊)

1.P3354 [IOI2005]Riv 河流

2.P4322 [JSOI2016]最佳團體

3.P4037 [JSOI2008]魔獸地圖

4.P4516 [JSOI2018]潛入行動

2.普通樹形$DP$

經歷過樹形揹包的洗禮,我們應該瞭解到了樹形$DP$的基本思路,接下來就開始真正的樹形$DP$吧!

1.最大權獨立集問題(題解)(我的程式碼)

本題作為樹形$DP$的經典例題,當然會在各種場合出現。本題要求父子節點二選一,使總價值最大,自然是一道典型的最大權獨立集問題板子。做這類題,我們可以在$DP$陣列上多開一維,這一維只有$0$或$1$組成,表示該節點選或者不選,然後在根節點處統計答案最大值即可。

2.最小權覆蓋集問題(覆蓋邊)(題解)(我的程式碼)

本題要求 在給定的樹上取最小的節點數,使所有邊都至少有一個端點在選中的集合中。 我們還可以仿照第一問的思路繼續解題-- 在$DP$陣列上多開一維,表示該節點選或者不選,然後在根節點處統計答案最小權值 。所以,只要我們深刻理解樹形$DP$的方法,就可以如法炮製做其他題。

3.最小權覆蓋集帶點權的問題(覆蓋點)(題解)(我的程式碼)

本題看上去像例題2的加強版,但其實本題要求守點,而不是像例題2一樣守邊。就是這一個看似細小的差距,卻導致了程式碼的千差萬別。例如下面這張圖:

如果我們只守1、4號點,那這一條鏈上的點就都可以守到,但2<->3這一條邊明顯沒有守到。所以本題中我們要考慮三種情況:

1.$x$節點被自己覆蓋,即選擇$x$點來覆蓋$x$點

2.$x$節點被兒子$y$覆蓋,即選擇$y$點來覆蓋$x$點

3.$x$節點被父親$fa$覆蓋,即選擇$fa$點來覆蓋$x$點

然後分類討論進行狀態轉移,最後在根節點處取$min(f[1][0],f[1][1])$作為代價的最小值。(因為根節點沒有父親節點,所以不用討論該情況)

CF1120D Power Tree(題解)(我的程式碼)

本題想到$DP$的思路並不難,但主要噁心在輸出每個點是否可能成為最優解,這就導致一道本來黃$\sim$綠的樹形$DP$題強行升紫。

思路:

我們用dp[u][0]表示以$u$為根節點的子樹將所有葉子節點控制的最小代價,用
dp[u][1]表示以$u$為根節點的子樹中還剩一個葉子節點未控制的最小代價。

  • $dp[u][1] = \sum\limits dp[v][0] - max(dp[v][0] - dp[v][1])$(我們可以貪心選擇一顆子樹,使$dp[u][1]$的代價最小)

  • $dp[u][0] = min(sum[u],dp[u][1] + val[u])$(顯然,我們可以不選當前節點取盡$u$的所有子樹,也可以選當前節點)

我們再建一堆輔助陣列,用於輸出答案,用g[u]表示$u$的兒子節點中dp[v][0]-dp[v][1]的最大值,用
num[u]表示dp[u][0]可以有多少個dp[v][1]轉移而來,用sum[u]表示所有dp[v][0]之和,再用can[u][1]表示可以通過dp[u][1]轉移到其父親節點can[u][0]表示可以通過dp[u][0]轉移到其父親節點。

  • 如果$can[u][0]=1$

  • $\circ$轉移到$can[v][0]$

  • $\circ$ $\circ$ 如果$num[u]>1$,則$can[v][0]=1$,因為$v$節點不一定傳遞到$dp[u][1]$

  • $\circ$ $\circ$ 如果$g[u]!=dp[v][0]-dp[v][1]$,即$v$節點一定傳遞到$dp[u][0]$,$can[v][0]=1$。

  • $\circ$ $\circ$ 如果$dp[u][0]==sum[u]$,即$v$節點一定傳遞到$dp[u][0]$,$can[v][0]=1$。

  • $\circ$轉移到$can[v][1]$

  • $\circ$ $\circ$ 如果$sum[u]-dp[v][0]+dp[v][1]+val[u]==dp[u][0]$,即貪心選擇的子樹是$v$節點,顯然$can[v][1]=1$。

  • 如果$can[u][1]=1$

  • $\circ$轉移到$can[v][0]$

  • $\circ$ $\circ$ 如果$num[u]>1$,則$can[v][0]=1$,因為$v$節點不一定傳遞到$dp[u][1]$

  • $\circ$ $\circ$ 如果$g[u]!=dp[v][0]-dp[v][1]$,即$v$節點一定傳遞到$dp[u][0]$,$can[v][0]=1$。

  • $\circ$轉移到$can[v][1]$

  • $\circ$ $\circ$ 如果$g[u]==dp[v][0]-dp[v][1]$,即貪心選擇的子樹是$v$節點,顯然$can[v][1]=1$。

又到了扔題時間

1.P4084 [USACO17DEC]Barn Painting G

2.P3174 [HAOI2009]毛毛蟲

3.P3621 [APIO2007]風鈴

4.P3574 [POI2014]FAR-FarmCraft

5.P3523 [POI2011]DYN-Dynamite

6.P4099 [HEOI2013]SAO

7.P3237 [HNOI2014]米特運輸

3.換根樹形$DP$

1.基本思路

  • 先以$1$號節點,進行一遍$dfs$,令$f[u]$表示以$u$為根節點的子樹的最優解。

  • 將根節點轉移到其兒子節點上,並不斷更新$f[to]$(假設$to$為$u$的兒子節點)

  • 將第二步推而廣之,逐漸更新完整顆樹

由上面的操作可以看出,換根$DP$其實就是不斷利用以$1$為根節點得到的資訊更新以其他點為根節點得到的資訊。

1.來一道入門題--找使所有結點的深度之和最大的根節點(題解)(我的程式碼)

作為入門題,當然是要讓我們熟悉換根$DP$基本思路的題。本題可以令$f[i]$表示以$i$號節點為根節點的樹的深度和,用根節點從$u$移動到$to$($u$的兒子節點)時以$to$為根節點這顆子樹的所有節點的深度$-1$,而其他節點的深度$+1$進行狀態轉移。

2.找使所有節點到當前點最短距離的點(題解)(我的程式碼)

一樣的板子和套路,不過因為極大值比較大,所以先以$f[1]$為極大值再更新其他值即可。(白白WA40pts)

3.求距離每個節點不超過k的點的權值和(題解)(我的程式碼)

4.找使該節點所有子樹大小和最大的點(題解)(我的程式碼)

5.詢問該節點經過改造後是否能成為重心(題解)(我的程式碼)

本題思路比較好想(改造該點子樹中最大節點數不超過$\frac{n}{2}$的子樹),但是碼程式碼時一定要養成好習慣,不要打出int to=e[i].nxt這樣的細節錯誤 (關鍵這種錯誤找了一個下午+一個晚上才找出來)

因為要去考$NOIP$,所以還是挖坑 (估計也不會填了,畢竟考完還要補文化)

我回來填坑了$!$

1.P6419 [COCI2014-2015#1] Kamp(題解)(我的程式碼)

2.P3647 [APIO2014]連珠線(題解)(我的程式碼)