1. 程式人生 > >刷油漆(樹形dp)

刷油漆(樹形dp)

“是啊,我該怎麼做呢?”小Ho想道,但是如果能很快就自己想出來那也就不是小Ho了,於是小Ho還是老老實實去請教了小Hi。

小Hi聽了小Ho的問題,道:“這個問題不是很簡單麼?來,我們再重複一下之前的步驟——先抽象你的問題。”

“好的!應該是這樣的——f(t, m)表示,在以t為根的一棵樹中,選出包含根節點t的m個連通的結點,能夠獲得的最高的評分,然後我們的答案就是f(1, M)!”身經百戰的小Ho也是隻需要一下點撥,立馬就答了出來。

“那麼你應該如何分解這個問題為子問題呢?”小Hi繼續問道。

“一般的思路會是這樣子的,首先我要包含根節點,然後與根節點連通的結點最開始便是根節點的子結點,而所有選擇的結點都要互相連通的話,那麼如果選擇某一棵子樹中的結點的話就勢必也需要選擇這棵子樹的根節點——所以就變成了一個規模小一些的子問題。比如在求解f(t, m)的時候,我先列舉t的第一個子結點t1中選出的結點數m1,然後列舉t的第二個子結點t2中選出的結點數m2……一直到t的最後一個子結點tk中選出的結點數mk,這樣就有f(t, m) = max{f(t1, m1) + f(t2, m2) + …… + f(tk, mk)} + v(t),並且需要保證m1+m2+...+mk+1=m。”小Ho答道。

小Hi搖了搖頭:“但是你不覺得這樣這個演算法就是指數級了麼?m1...mk可能有的方案數可是非常多的呢!”

“唔……我知道了,這裡不是和無限揹包問題很像麼?我可以不用單獨的求解每一個f(t, m)而是針對於每一個t,同時求解它的f(t, 0..M),這樣的話,我就可以把m視作揹包容量,把每個子結點t_child都視作一件單位重量為1的物品,但是和揹包問題不同的是,這件物品的總價值並不是單位價值乘以總重量,而是重量為m_child的該物品的價值為f(t_child, m_child),這樣我就可以像無限揹包問題一樣,用這樣的方法來進行求解!

“沒錯呢!但是你這樣的話不會導致f(t_child, m_child)計算很多次麼?”小Hi也是故意要考一考小Ho。

“這你就小瞧我了,我學了這麼久動態規劃難道還不知道我可以以後序遍歷的方式訪問這棵樹,這樣當計算f(t, 0..M)的時候,我就已經計算出了所有的f(t_child, m_child)的值,如果我將這些值儲存在陣列中的話,我就不需要再遞迴計算了!”小Ho信心滿滿的答道。

“真聰明!那你還不快去寫程式?你的油漆都要乾了哦!”