1. 程式人生 > 其它 >基礎線性dp

基礎線性dp

基礎線性dp

鍾昊原

一、dp的引入

動態規劃(簡稱dp)是指把一個問題分解為若干個子問題,通過區域性最優解得到全域性最優的一種演算法策略或者說一種思想方法。簡單來講,就是說用一個數組表示我們要求的問題的答案:如果你知道前一個問題的答案,你就可以推出後一個問題的答案。dp有以下幾個常見的概念:

1.狀態:指當前所考慮的子問題的情況。例如揹包的已用體積、區間的起止點,以及用狀態壓縮手段壓縮後的狀態。

2.狀態轉移:指由前一個子問題的答案推出當前問題的答案。一般來講會由一個表示賦值的等式給出,稱為狀態轉移方程。

3.無後效性:指當前子問題的處理策略與後邊問題的解答無關。要記住我們是從子問題的答案推出新問題的答案,與這個子問題答案怎麼來的無關。

總的來講,dp一般有以下三個步驟:

1.設計狀態:指設計出合適的dp陣列以及規定dp陣列的含義。設計出的dp陣列要能夠形容各種狀態並且能無後效性地在狀態之間進行轉移。

2.推理狀態轉移方程:顧名思義,關鍵在於如何從已知問題的答案推出當前問題的答案,有的時候需要多個方程,有的時候一個方程要包含多個子狀態。

3.確定邊界條件:遞推的初值或者說記憶化搜尋的回溯條件,以及各個陣列的初值。

舉個例子,以在學習遞推時就學過的臺階問題為例,如果把這個當成dp:

問題:一個人走樓梯,一次可以跨一階,可以跨兩階,求到達第n階有多少種辦法。

設計狀態:dp[i]表示到達第i階的方法數

狀態轉移:第i階可以來自第(i-1)階或第(i-2)階,dp[i]=dp[i-1]+dp[i-2]

邊界條件:到達第1階只有一種辦法,dp[0]=dp[1]=1

二、線性結構上的dp

線性dp往往指在一個序列上進行的dp,當然也可能有兩個甚至多個序列。一般來講,線性dp的三個步驟分別有以下特點:

設計狀態:至少有一維表示當前考慮的物件在數列上的位置。

狀態轉移:必須找到這條線上前面的位置的dp值來推出當前位置的dp值。

邊界條件:第一個位置單獨討論。

【例1 最大子段和】

給定一串數,求其中某一連續序列,使得這個序列中的數字之和最大,例如,5 -7 6 2 -5 -2 8 -3,要取到一段和最大的連續序列應該是“6 2 -5 -2 8”答案是9。

分析:按照三個步驟設計dp。首先是dp[i]的含義:如果它僅表示前i個數的子序列中最大子段和長度,那麼在這個最大子段和不包含a[i]的情況下,dp[i+1]將無法由dp[i]得出。所以dp[i]的準確含義是“前i個數的子序列中,包含a[i]的子段,的最大和”。然後狀態轉移方程來自兩個決策:既然a[i]一定要取,那麼a[i]是繼承dp[i-1],還是自立門戶?所以dp方程就是在兩個決策中取max:dp[i]=max{dp[i-1]+a[i],a[i]}。最後注意邊界條件是dp[1]=a[1],這在寫程式碼時不用特殊處理。最終答案為max{dp[i]},時間複雜度為a[i]。

【例2 最長公共子序列(LCS)】

給兩個子序列A和B,求長度最大的公共子序列。例如1 5 2 6 8 7和2 3 5 6 9 8 4 的最長公共子序列為 5 6 8(另一個解是2 6 8)。(如圖)

A B C B D A B

B D C A B A

分析:設dp[i][j]為A1,A2,...,Ai和B1,B2,...,Bj的LCS長度,則當A[i]=B[j]時,dp[i][j]=dp[i-1][j-1]+1,否則dp[i][j]=max{dp[i-1][j],dp[i][j-1]},時間複雜度為O(nm)。注意邊界條件是所有的初始值為0,寫程式碼時仍然不用專門處理。

【例3 最長不下降子序列(LIS)】

最長不下降子序列是一個不嚴格遞增,允許不同位元素相等的序列。而它是子序列也表明它的長度一定小於等於原序列。且子序列在原序列的位置不一定連續。例如1 2 3 4 5 5是一個不下降序列,而1 3 2 2 5 5的最長不下降子序列就是1 2 2 5 5。

分析:這個序列的每一個數為止都有一個解, 作為子問題的解. 後面的問題的解就是從這些子問題的最優解繼承過來的,所以設dp[i], 表示截止到Ai的解。

當下一個數要加入來的時候, 有兩種情況

1.前面的數都比當前數更大, 因此以這個數為止的最長不下降子序列的長度就是1. 遍歷到第一個數的情況也包含在內。

2.前面的數有不比當前數大的, 那麼這個數的結果dp[i] = max(dp[i], dp[j] + 1). 這個過程遍歷前面所有數的dp[j]進行比較。

最後的答案就是所有dp[i]裡面的最大值。

【思考題 最長不下降公共子序列】

兩個序列求公共子序列,但是這個子序列不僅要最長,還要滿足它不嚴格單調。

提示:把例2和例3的dp結合起來,例2的解法為該題提供框架,例3的dp方程可以往例2的基礎上新增一些條件。

三、揹包問題

揹包是動態規劃的一個分支。總的來講,揹包問題可以描述為:給定一組物品,每種物品都有自己的重量w[i]和價格v[i],在限定的總重量內,我們如何選擇,才能使得物品的總價格最高。這種問題的動態規劃過程一般有以下特點:

狀態設計:肯定有一維表示揹包當前裝入東西的總質量

狀態轉移:現在總質量為j的揹包,裝了質量為w[i]的物體之前的質量是j-w[i],所以dp[j]要從dp[j-w[i]]來

邊界條件:揹包為空的時候價值是0(需要特別注意的一點是,千萬不要用dp[w[i]]=v[i]來給dp陣列賦初值,一定要用dp[0]=0。)

【例1 01揹包】

有一個容量為 V 的揹包,和一些物品。這些物品分別有兩個屬性,體積 w 和價值 v,每種物品只有一個。要求用這個揹包裝下價值儘可能多的物品,求該最大價值,揹包可以不被裝滿。在最優解中,每個物品只有兩種可能的情況,即在揹包中或者不在揹包中(揹包中的該物品數為0或1),因此稱為0-1揹包問題。

分析:對於每一個物品,有兩種結果:能裝下或者不能裝下。第一,包的容量比物品體積小,裝不下,這時的最大價值和前i-1個物品的最大價值是一樣的。第二,還有足夠的容量裝下該物品,但是裝了不一定大於當前相同體積的最優價值,所以要進行比較。由上述分析,子問題中物品數和揹包容量都應當作為變數。因此子問題確定為揹包容量為j時,求前i個物品所能達到最大價值。“狀態”對應的“值”即為揹包容量為j時,求前i個物品所能達到最大價值,設為dp[i][j]。初始時,dp[0][j](0<=j<=V)為0,沒有物品也就沒有價值。所以說dp方程是

j<w[i], dp[i][j]=dp[i-1][j]//揹包裝不下該物品,最大價值不變

j>=w[i], dp[i][j]=max{dp[i-1][j-w[i]]+v[i],dp[i-1][j]}//和不放入該物品時同樣達到該體積的最大價值比較

但是空間複雜度能否優化?我們發現dp[i][j]的轉移只與dp[i-1][j-w[i]]和dp[i-1][j]有關,即僅與二維陣列本行的上一行有關。因此,我們可以將二維陣列優化為一維陣列。不過這裡要注意兩點:1.j<w的狀態轉移方程不再需要了。2.為保證每個物品只能使用一次,我們倒序遍歷所有j的值,這樣在更新dp[j]的時候,dp[j-w[i]]的值尚未被當前物品i修改,就不會出現一個物品重複使用的問題。此時的dp方程簡化為

j>=w[i], dp[j]=max{dp[j-w[i]]+v[i],dp[j]}

【例2 完全揹包】

與01揹包不同的是,完全揹包每個物品可以使用多次,或者也可以理解為每個物品的數量有無窮多個。

分析:在講解01揹包的優化時我們提到“為保證每個物品只能使用一次,我們倒序遍歷所有j的值”。那如果正序遍歷,更新dp[j]的時候,dp[j-w[i]]的值已經被i修改,i就可以重複使用了。

(思考)在前兩個模板的基礎上,如果限制條件不止一個(例如同時限制體積和質量),應該如何解決?如果收益有A、B兩種,要求保證A最大的前提下B儘可能大,應該如何解決?

【例3 金明的預算方案】

在01揹包的基礎上,將物品分為兩種:主件與附件,附件是從屬於某個主件的,如果要買歸類為附件的物品,必須先買該附件所屬的主件。每個主件可以有0個、1個或2個附件。

分析:先考慮1個附件的情況。對於有1個附件的主件而言,有3種選擇方式:1.什麼都不選;2.只選主件;3.全部選上。而我們可以把這三種情況拆成2個不同的物品,即“純主件”和“主件+附件”。那麼2個附件同理,拆成4個物品:“純主件”、“主件+附件A”、“主件+附件B”、“主件+附件A+附件B”。然後執行普通的01揹包就好了。

【思考題 分組揹包】

物品大致可分為k組,每組中的物品相互衝突,最大的價值是多少。