1. 程式人生 > >經典動態規劃——揹包問題系列一

經典動態規劃——揹包問題系列一

經典動態規劃——揹包問題系列一

複賽前發一波部落格,雖然意義不是很大了……

本篇講的是揹包問題基礎

01揹包問題

簡述

有N件物品和一個容量為V的揹包。第i件物品的體積是c[i],價值是w[i]。求解將哪些物品裝入揹包可使這些物品的費用總和不超過揹包容量,且價值總和最大。

思路

動態規劃的基本題,揹包問題之母。

對動態規劃有一定了解的人應該都應理解它的原理和方程。

所謂動態規劃,就是把問題分成互相聯絡的多個階段決策,每一步決策都能影響答案。當然,各個階段決策的選取不是任意確定的,它依賴於當前面臨的狀態,又影響以後的發展。那麼我們需要確定,各階段之間的關係是什麼以及各狀態如何決策才能使答案最優。

在揹包問題中,我們通常把揹包容量設為狀態。那麼我們需要決策,到底如何放置物品,才能使各階段揹包在有限容量內裝價值最多的物品?

首先我們需要知道:揹包容量大的狀態一定由容量小的狀態轉移過來,因為我們要不斷選物品,這樣是總重量越來越大。現在我們用f[i][j]示前i件物品恰放入一個容量為v的揹包可以獲得的最大價值。

我們只要列舉物品,在有限的空間裡取物品,並一直取max,就能在規定揹包空間內跑完。

\[ dp[i][v] = max(dp[i-1][v],dp[i-1][v-c[i]]+w[i]) \]

為什麼要逆著列舉?因為容量大的狀態要從容量小的狀態轉移過來,而我們要固定每個容量去裝物品,所以要從大到小列舉。

程式碼

for(int i=1;i<=n;i++) 
    for(int j=v;j>=0;j--)
    {
        if(j >= c[i])
            dp[i][j]=max(dp[i-1][j-c[i]]+w[i],dp[i-1][j]);
        else
            dp[i][j] = dp[i-1][j];
    }

優化

將我們可以優化一維空間,方程就變成了:
\[ dp[v] = max(dp[v],dp[v-c[i]] + w[i]) \]

程式碼

for(int j=v;j>=w[i];j--)
    dp[j] = max(dp[j],dp[j-c[i]]+w[i]);

拓展:揹包方案數問題

簡述

還是01揹包,現在讓你求取得物品總價值最大時的方案數。

思路

類似地,還是思考狀態是什麼以及如何從上一個狀態轉移過來。我們還設狀態為前i個物品,以及揹包體積v。動歸陣列存的是方案數,然後思考如何轉移。

我們設dp[i][j]存的是最大價值,f[i][j]存的是方案數。

類比之前的方程,若dp[i][j]由dp[i-1][j]轉移過來,那麼f[i][j]的應該等於f[i-1][j];若dp[i][j]由dp[i][j-c[i]]轉移過來,那麼f[i][j]也應該等於f[i][j-c[i]];若dp[i][j-c[i]]與dp[i-1][j]相等,那麼根據加法原理,f[i][j]應是f[i-1][j]與f[i][j-c[i]]的和。

所以我們得出了方程:
\[ f[i][j] = \begin{cases} f[i-1][j]\ \ \ \ \ \ (dp[i-1][j] > dp[i][j-c[i]])\\ f[i][j-c[i]]\ \ \ (dp[i-1][j] < dp[i][j-c[i])\\ f[i-1][j] + f[i][j-c[i]]\ \ (dp[i-1][j] = dp[i][j-c[i]) \end{cases} \]

拓展:數字組合問題

簡述

給你n塊錢,有m種錢幣,每種錢幣只能用一次,問組成n塊錢有多少種方法。

思路

既然你要考慮用dp做,那你肯定會毫無疑問地去選擇前i個物品,選j個和錢數k作為狀態,用揹包來計算方案數。

也就是
\[ dp[i][j][k] += dp[i-1][j][k] \]

\[ if(k >= v[i]) \\ dp[i][j][k] += dp[i-1][j-1][k-v[i]] \]

事實上這樣會超時。

所以要怎麼做?

先將物品排升序,列舉狀態\(i\),表示第\(i\)個物品是未被選的物品中價值最小的一個物品。

那麼價值比它小的物品,都在它左面,而且都會被選,把他們用字首和維護起來。

現在你剩餘一些錢j,從後面選取一些物品使價值最大但不超過j,這就是揹包。

所以一共二維,時間複雜度空間複雜度皆降低。