學習筆記——樹形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)$(因為每一個節點只會被遍歷一次
二、樹形$DP$的幾種型別
1.揹包類樹形$DP$
既然我們前面將揹包和樹形$DP$聯絡在一起學了,那我們就先從我們熟悉的揹包類入手吧!(這樣能增加我們的信心,減少我們對樹形$DP$的畏懼心理)
通過本題,我們就可以大致瞭解樹形$DP$的思路--從根節點出發不斷向子樹進行搜尋,再將子樹的資訊合併到該節點上。(注意:本題要考慮只選該節點與子樹連線的這一條邊的情況)
本題我們又學會了一種思想--建虛點。因為本題是在森林中選一定的節點(選完父節點才可以選子節點),但我們又不可以只由一棵樹得到最優解,所以我們可以考慮建一個虛點($0$),令它向所有樹的根節點連一條邊,接著只要對以虛點為根節點的子樹進行樹形$DP$求出最優解
通過本題,我們要學會在樹形$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$秒可以取得的最大價值。這樣可以極大地減少思考難度。
繼續挖坑 (挖坑太多後面會不會填不完啊)
2.普通樹形$DP$
經歷過樹形揹包的洗禮,我們應該瞭解到了樹形$DP$的基本思路,接下來就開始真正的樹形$DP$吧!
本題作為樹形$DP$的經典例題,當然會在各種場合出現。本題要求父子節點二選一,使總價值最大,自然是一道典型的最大權獨立集問題板子。做這類題,我們可以在$DP$陣列上多開一維,這一維只有$0$或$1$組成,表示該節點選或者不選,然後在根節點處統計答案最大值即可。
本題要求 在給定的樹上取最小的節點數,使所有邊都至少有一個端點在選中的集合中。 我們還可以仿照第一問的思路繼續解題-- 在$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])$作為代價的最小值。(因為根節點沒有父親節點,所以不用討論該情況)
本題想到$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
4.P3574 [POI2014]FAR-FarmCraft
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$進行狀態轉移。
一樣的板子和套路,不過因為極大值比較大,所以先以$f[1]$為極大值再更新其他值即可。(白白WA40pts)
3.求距離每個節點不超過k的點的權值和(題解)(我的程式碼)
5.詢問該節點經過改造後是否能成為重心(題解)(我的程式碼)
本題思路比較好想(改造該點子樹中最大節點數不超過$\frac{n}{2}$的子樹),但是碼程式碼時一定要養成好習慣,不要打出int to=e[i].nxt
這樣的細節錯誤 (關鍵這種錯誤找了一個下午+一個晚上才找出來) 。
因為要去考$NOIP$,所以還是挖坑 (估計也不會填了,畢竟考完還要補文化)
我回來填坑了$!$