經典動態規划——背包問題系列一
復賽前發一波博客,雖然意義不是很大了……
本篇講的是背包問題基礎
01背包問題
簡述
有N件物品和一個容量為V的背包。第i件物品的體積是c[i],價值是w[i]。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量,且價值總和最大。
思路
動態規划的基本題,背包問題之母。
對動態規划有一定了解的人應該都應理解它的原理和方程。
所謂動態規划,就是把問題分成互相聯系的多個階段決策,每一步決策都能影響答案。當然,各個階段決策的選取不是任意確定的,它依賴於當前面臨的狀態,又影響以后的發展。那么我們需要確定,各階段之間的關系是什么以及各狀態如何決策才能使答案最優。
在背包問題中,我們通常把背包容量設為狀態。那么我們需要決策,到底如何放置物品,才能使各階段背包在有限容量內裝價值最多的物品?
首先我們需要知道:背包容量大的狀態一定由容量小的狀態轉移過來,因為我們要不斷選物品,這樣是總重量越來越大。現在我們用f[i][j]示前i件物品恰放入一個容量為v的背包可以獲得的最大價值。
我們只要枚舉物品,在有限的空間里取物品,並一直取max,就能在規定背包空間內跑完。
為什么要逆着枚舉?因為容量大的狀態要從容量小的狀態轉移過來,而我們要固定每個容量去裝物品,所以要從大到小枚舉。
代碼
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];
}
優化
將我們可以優化一維空間,方程就變成了:
代碼
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]]的和。
所以我們得出了方程:
拓展:數字組合問題
簡述
給你n塊錢,有m種錢幣,每種錢幣只能用一次,問組成n塊錢有多少種方法。
思路
既然你要考慮用dp做,那你肯定會毫無疑問地去選擇前i個物品,選j個和錢數k作為狀態,用背包來計算方案數。
也就是
事實上這樣會超時。
所以要怎么做?
先將物品排升序,枚舉狀態\(i\),表示第\(i\)個物品是未被選的物品中價值最小的一個物品。
那么價值比它小的物品,都在它左面,而且都會被選,把他們用前綴和維護起來。
現在你剩余一些錢j,從后面選取一些物品使價值最大但不超過j,這就是背包。
所以一共二維,時間復雜度空間復雜度皆降低。