(C++)DP動態規劃

ComputerEngine發表於2024-03-16

天下苦DP久已。

​ DP非常重要,2022年藍橋杯應該改名為DP杯,至於2023年那個我覺得應該叫做暴力杯。

​ DP的核心思想在於,透過前面幾個狀態來推導下一個資料是什麼,也就是走一步是一步。其本質實際上是記憶化搜尋,但是有些玄學的事情就是,有時候記憶化會因為玄學遞迴問題TLE,但DP的那四五層for迴圈就迷之過了。


​ DP題的核心在於兩個事情:

一個就是你要知道這題可以DP

​ 是否有後效性,對於某個問題,如果我們只關注某個狀態,只關注狀態的值,而不關注該狀態是如何轉移過來的,那麼就是一個無後效性的問題,此時可以使用DP解決。

​ 用人話來說就是,只會用上一步計算出下一步,前面其他步驟都不對現在的步驟產生任何影響。

 大問題的最優解可以由小問題的最優解推出,這個性質叫做“最優子結構性質”。

就是說你能從一個小問題一路推到問題本身,就像斐波那契數列一樣,你得從1+1開始推,然後一路推到你當前需要的數字裡頭。

然後就是你要知道怎麼得到狀態轉移方程

​ 這步才是真正麻煩的事情。狀態轉移方程的目的就是利用前一狀態,透過特定但是通用的式子,來得到當前狀態的最優答案。但到底用什麼做狀態,有個很容易理解的事情。很多人都說01揹包問題才是DP的經典入門題。我覺得不是。

​ 有請真正經典例題:NOIP2004普及組 花生採摘

​ 題目不再在這裡複述了,自個去裡頭看。但是為什麼我說這題才是經典入門題呢?這題能很好並且很簡單的體現DP該有的一切。他的狀態轉移方程原理非常清晰明瞭:\(f[y][x] = max(上方點最大采摘數, 左方點最大采摘數) + 當前點花生數\)。在方程裡,我們可以立刻確認一件事情:狀態是位置,方案最優解來自於當前位置的花生數和上一步可以得到的最大花生數。

​ 那麼到底什麼可以作為狀態?你可以把狀態轉移方程比喻成一個特殊的函式,這個函式需要你輸入特定的自變數才能得到結果,而那些自變數就如同構成了座標軸一樣(就好像花生採摘的平面座標一樣)構建了一個座標系,我們需要用當前的座標,配合題目條件去計算下一個座標在哪。用這個思維去看01揹包問題 - NOIP2005普及組 採藥,到底是什麼可以構成我們的座標系呢?價值要作為答案的輸出,所以肯定不是,那麼就剩下第幾種藥和已用的時間,換成裸的01揹包描述就是,我們用當前物品編號剩下多少時間組成了自變數,得到了二維的方程。

​ 選用當前物品編號(而不是選用已經選擇了多少個物品)作為其中一個狀態的原因是,我們需要確認我們選了什麼,這樣才能從其他狀態得到對應的值。如果用的是取了多少個物品,我們並不知道到底取用了哪些物品,那就找不到狀態在陣列中對應的位置,就沒法把狀態轉移方程推導下去。

​ 那麼到了多重揹包裡呢就是多列舉一下每個物品塞多少個,塞不塞得下,因為多重揹包本質就是這個物品重複幾次出現而已。至於完全揹包問題,則是透過倒著推揹包空間來獲得解法。

​ 透過以上總結可以發現,可以作為狀態的引數必須是可用於計算上/下一個狀態是什麼,這樣才能找到下一個狀態位置,搞出狀態轉移,進而使用狀態轉移方程計算結果。

相關文章