前言:覺得二進位制很nb,想學狀態壓縮,可是不會動態規劃,連揹包都不會的那種,從頭開始吧!
0.問題:給定一組物品,每種物品都有自己的重量wi(i=1,2,3…n)和**vi(i=1,2,3…,n),在限定的總重量(w)內,我們如何選擇,才能使得物品的總**最高。
1.初始化:我們定義dp[i][j]表示在揹包容量為j時從0到編號為i的物品所能裝進揹包的最大價值,從最簡單的情況開始,dp[i][0] (](i=0,1,…,n-1)表示在揹包容量為0時前i個物品所能裝進揹包的最大價值,很明顯全部的值都是0,因為此時無法裝下任何物品.
for
(int i=
1;i<=n;i++
)dp[i][0
]=0;
接著是dp[1][j] (j=0,1,2,…,w),表示在揹包容量為0時編號為1的物品所能裝進揹包的最大價值,如果w1<=j,那麼第1個物品可以裝下,否則揹包仍為空。注意這時候的j並不是w,也就是我們假設存在另外的揹包,體積分別為0,1,2,…,w。
for
(int j=
0;j<=w;j++
)
3.對於任意乙個物品i,它只有取和不取兩種選擇,如果它沒有被取,那當前揹包內物品的最大價值和dp[i-1][j]是一樣的,相當於dp[i][j](表示在揹包容量為j時前i個物品所能裝進揹包的最大價值)==dp[i-1][j] (表示在揹包容量為j時前i-1個物品所能裝進揹包的最大價值),因為根本沒有新增物品,價值不會增加也不會減少。
隨便問乙個dp[i][j]是多少,我們知道它表示在揹包容量為j時前i個物品所能裝進揹包的最大價值。如果第i個物品沒有被裝進去,那麼dp[i][j]=dp[i-1][j],如果第i個被裝進去,dp[i][j]=dp[i-1][j-w[i]]+v[i],表示在揹包容量為j時前i個物品所能裝進揹包的最大價值為在揹包容量為i-1時前j-w[i]個物品所能裝進揹包的最大價值再加上第i個物品本身的價值。注意現在dp[i-1][j-w[i]]並不是表示把第i-1個物品去掉,而是這個狀態的最大價值,它可以是有i-1或沒有i-1個物品的!
不會吧不會有人真的這麼想吧好吧之前的我。
for
(int i=
1;i<=n;i++
)
4.如果題目只需要最終揹包容量為w時前n個物品所能裝進揹包的最大價值,那麼明顯dp[小於n][任何數] 都是不需要的,怎麼省下這些空間呢?對於上面的空間複雜度為o(n*w)的陣列,我們觀察發現,dp[i][j] 的產生除了題目給定的w[i],v[i],就只有dp[i-1][k] (k=0,1,2,…j)。但是dp[小於i][任何數]都不是我需要的。我們用神奇的滾動陣列來降低空間複雜度。
for
(int i=
1;i<=n;i++
)
證明正確性:
如果第一層迴圈走到了i-1,那麼我們可以仍然把陣列看成是dp[i-1][…],從dp[i-1][w],一直更新到dp[i-1][0]。然後我們讓dp[j] (j=w,w-1,…,2,1,0)=dp[i-1][j] (j=w,w-1,…,2,1,0) 第i-1層走完之後到了第i層,那麼現在開始來更新第乙個數dp[i][w]了。它會等於max(dp[i-1][w],dp[i-1][w-w[i]+v[i]),也就是上一層的答案全部被保留下來被第i層使用了。
dp[i]
[w]=
max(dp[i-1]
[w],dp[i-1]
[w-w[i]
]+v[i])=
max(dp[w-w[i]
],dp[w-w[i]
]+v[i]
)
,在這個時候我們順便把dp[w]更新成dp[i][w], 然後重複重複直到dp[0]=dp[i][0],最後dp[j] (j=w,w-1,…,2,1,0)=dp[i][j] (j=w,w-1,…,2,1,0) 這樣子整個dp陣列都是dp[i][…]的,和dp[i-1][…]都完全無關了,我們就安安心心繼續去搞dp[j+1],dp[j+2]了。
接著還有乙個小問題:為什麼遍歷j的時候會反向遍歷呢?如果還是正向遍歷,那麼先用二維的dp觀察一下:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) //(*式),
翻譯成dp陣列就是:
dp[j]=max(dp[j],dp[j-w[i]]+v[i])//(#式)
到了後面某乙個j=j+w[i]的時候,我們想要的是:
dp[i][j]=max(dp[i-1][j],dp[i-1][j]+v[i])
用dp表示:
dp[j]=max(dp[j],dp[j-w[i]]+v[i])=max(dp[j],dp[j]+v[i])
可是!你發現了嗎?在本來和(*式)作用相同的(#式)中,我們把dp[j]更換成了dp[i][j]的意義,替換了原來的dp[i-1][j]的意義,相當於原來的dp[i-1][j]的值都被dp[i][j]覆蓋了,等dp[i][j]要用的時候它只能用dp[i][j]去更新自己 但它要的是dp[i-1][j]啊。所以這個陣列就全亂套了。
反向遍歷更新有什麼好處?
dp[j]=max(dp[j],dp[j-w[i]]+v[i])
,即
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])
,我們先更新大的dp[j],它捷足先登把比它小的代表dp[i-1][j]含義的dp[j]給用了再說,現在dp[j]被更新成dp[i][j]的含義也沒有任何關係,因為等一下只有比它小的jj需要被更新,那個時候絕對用不著它了,因為
dp[jj]=max(dp[jj],dp[jj-w[i]]+v[i])
,dp[jj]用到的dp[jj-w[i]]的jj-w[i]一定是比j小的(無聊的說明)。
01揹包 1維 滾動陣列
這裡先說一下二維的。01揹包 設物品有n件物品,揹包容量為w int w 代表n件物品的價值 int pw 代表n件物品各佔的容量 int f n 50 w 50 最優解二維陣列 f i j 陣列 代表存i件物品在容量為j的揹包中得到的價值 void package 01 printf d n f ...
揹包九講 01揹包問題(dp 滾動陣列)
現在有n件物品和乙個容量為v的揹包。第i件物品的費用是cost i 價值是value i 每個物品最多只能選一次,求解在不超過揹包容量的限制下,如何選取物品組合能使收益最大化?題型有兩種,一種要求揹包恰好放滿,一種不要求揹包恰好放滿 現在考慮第二種題型,即不要求揹包恰好放滿 將問題分解成子問題 有i...
0 1揹包使用滾動陣列壓縮空間
所謂滾動陣列,目的在於優化空間,從上面的解法我們可以看到,狀態轉移矩陣使用的是乙個n v的陣列,在求解的過程中,我們可以發現,當前狀態只與前一狀態的解有關,那麼之前儲存的狀態資訊已經無用了,可以捨棄的,我們只需要空間儲存當前的狀態和前一狀態,所以只需使用2 v的空間,迴圈滾動使用,就可以達到跟n v...