揹包三連 01揹包 多重揹包 完全揹包

2022-05-20 20:59:40 字數 3695 閱讀 3206

題目:有n件物品需要放入乙個容量為v的揹包,第i件物品的體積為vi,他的價值為wi,求解將哪些物品裝入揹包可以使得總價值最大。

題目特點:每種物品只有一件,可以選擇或者不放。

用子問題定義狀態:即用dp[ i ][ j ] 表示前i件物品恰放入乙個容量為 j 的揹包可以獲得的最大價值。則其狀態轉移方程為:dp[ i ][ j ] = max(dp[i - 1][ j ], dp[i - 1][j - vi] + w[ i ]);

下面將這個方程解釋一下,將前i個物品放入容量為 j 的揹包中這個子問題,若只考慮第 i 件物品的策略(放或不放),那麼就可以轉化為「前i - 1件物品放入容量為 j 的揹包中,此時產生的

價值為dp[ i - 1] [ j ],如果放入第 i 件物品,那麼問題就轉化為前 i - 1 件物品放入剩下的容量為j - ci 的揹包中,此時能獲得的最大價值為dp[ i - 1][ j - c[i] ]再加上通過第 i 件物品獲得的價值w[ i ]。

滾動陣列優化:我們在計算都是利用dp[ i - 1 ][ 0 ..... v] 計算出了dp[ i ][ 0 .... v] ,由上式可以看出dp[ i ]只與dp[ i - 1]有關,那麼我們可不可以省略這一組呢?答案是可以的,如果我們省略第一

維,那麼我們就可以得出另乙個狀態轉移方程dp[ j ] = max(dp[ j ], dp[ j - v[ i ] ] + w[ i ] )。在這裡我們的狀態轉移方程就相當於dp[ i ][ j ] = max(dp[i - 1][ j ], dp[i - 1][j - vi] + w[ i ]);那麼在二維狀態下

我們利用dp[ i - 1] [ j - v[ i ] ] + w[ i ] 推出了dp [ i ][ j ],這裡第二維是從小到大推的,所以倒序和順序不影響結果,但是如果我們將第一維度去掉這樣還可以麼?我們可以考慮,如果 j 依然是從v [ i ] - > v還可行麼,

答案是不可行,因為如果還是順序訪問的話,我們只是用dp[ i ][j - v[ i ] ] + w[ i ] 推出了dp [ i ][ j ],這與原題不符合。

初始化:01揹包問題一般分為兩種,第一種為恰好裝滿揹包,第二種為給定揹包求解能帶走的最大值而不必恰好裝滿。在求解恰好裝滿的揹包中我們需要將dp[ 0 ] 初始化為0,其餘的都初始化為 -inf,

為啥要這樣呢,由於需要得到將揹包完全裝滿的狀態,所以我們將象徵著揹包裝滿的dp[ 0 ]初始化為0,其餘的皆為-inf,這樣的話只有達到剛好裝滿狀態的裝載方法才會依次滾動下來,未裝滿的情況並不會被保留。

1

void

zero_one_pick()

完全揹包

題目:有n件物品需要放入乙個容量為v的揹包,第i件物品的體積為vi, 他的價值為wi,每件物品都有無限多個,求解將哪些物品放入揹包可以使得揹包中的總價值最大。

題目特點:每種物品有無窮件。

和01揹包不一樣的是所有物品都有不止一件,所以我們很容易可以得出狀態轉移方程:dp[ i ][ j ] = max(dp[i - 1][j - k * vi] + k * w[ i ])(0 <= k * c[ i ] << v);參照01揹包的方程這個式子也很容易得出。

轉換為01揹包問題:我們可以知道對於每一種物品,我們把他拆分成1 ~ v / c[ i ]件同樣的物品,然後對於所有物品我們進行與01揹包相同的計算即可得出正確答案。但是這樣做並不會給問題帶來時間複雜度上的

優化,那麼我們可以考慮另一種拆分方式,可以將第 i 件物品拆分為體積為v[ i ] * 2 ^ k,所帶來的價值為w[ i ] * 2^k的多個物品,這裡v[ i ] * 2 ^ k <= v;我們可以知道,對於物品 i 組成的每一種拆分情況我們都可以利用

對應的二進位制得到,所以顯而易見我們演算法得正確性。

我們可以給出一維陣列滾動情況下該思路的偽**:

f[0..v ] ←0

for i ← 1 to n

for v ← ci to v

f[v] ← max(f[v], f[v − ci ] + wi)  

你會發現,這個偽**與01揹包問題的偽**只有v的迴圈次序不同而已。 為什麼這個演算法就可行呢?首先想想為什麼01揹包中要按照v遞減的次序來 迴圈。讓v遞減是為了保證第i次迴圈中的狀態f[i, v]是由狀態

f[i − 1, v − ci ]遞 推而來。換句話說,這正是為了保證每件物品只選一次,保證在考慮「選入 第i件物品」這件策略時,依據的是乙個絕無已經選入第i件物品的子結果f[i − 1, v − ci ]。而現在完全揹包的特點恰是每種物

品可選無限件,所以在考慮「加 選一件第i種物品」這種策略時,卻正需要乙個可能已選入第i種物品的子結果f[i, v − ci ],所以就可以並且必須採用v遞增的順序迴圈。這就是這個簡單的 程式為何成立的道理。 值得一

提的是,上面的偽**中兩層for迴圈的次序可以顛倒。這個結論有 可能會帶來演算法時間常數上的優化。 這個演算法也可以由另外的思路得出。例如,將基本思路中求解f[i, v−ci ]的 狀態轉移方程顯式地寫出來,代入

原方程中,會發現該方程可以等價地變形成 這種形式: f[i, v] = max(f[i − 1, v], f[i, v − ci ] + wi)。

1

void

complete_pick()

6}

7 }

多重揹包

題目:有n件物品需要放入乙個容量為v的揹包,第i件物品的體積為vi, 他的價值為wi,每件物品都有m[ i ] 個,求解將哪些物品放入揹包可以使得揹包中的總價值最大。

題目特點:每種物品有有限多件。

這個問題和完全揹包很相似,我們只需要將完全揹包的**稍作改動即可得出dp[ i ][ j ] = max(dp[i - 1][j - k * vi] + k * w[ i ])(0 <= k <= m [ i ]);

轉化為01揹包問題:我們依然可以像完全揹包一樣將所有物品拆分為1~m[ i ]件物品,這樣做的效率等同於上面的方法,所以我們依舊考慮二進位制拆分。

將第 i 種物品我們仍然進行拆分,拆分之後的的物品 i 的價值為k * w[ i ],體積為k * v[ i ],k取1, 2, 4, ....2^(k - 1), m[ i ] - 2 ^k + 1 > 0。即 k 是滿足m [ i ] - 2^k + 1 > 0的最大整數。這樣我們就將原本的m[ i ] 件

物品拆分為log m件物品,我們很容易可以知道我們新切分的這些物品可以組成物品 i 的狀態數的所有狀態,即0......m[ i ]。這樣使得演算法的複雜度得到了較大的提公升。

1 #include 2 #include 3

using

namespace

std;45

const

int maxn = 1000 + 5, maxe = 1e4 + 5;6

intn, v, v[maxn], w[maxn], dp[maxe], c[maxn];78

void

mutiple_pack()

15int k = 1, num =c[i];

16while(k <=num)

22for(int j = v; j >= num * v[i]; j --)

23 dp[j] = max(dp[j], dp[j - num * v[i]] + num *w[i]);24}

25}2627

intmain ()

針對填滿多重揹包的單調佇列優化的o(vn)的優化演算法:

單調佇列已經在之前的一篇部落格中講到,這裡不再贅述,這是之前部落格的鏈結

揹包 01揹包,完全揹包,多重揹包

哈哈 01揹包 f i v max 完全揹包 f i v max 多重揹包 f i v max include include include include include define maxn 1000 using namespace std int n,cap int w maxn 重量 花...

01揹包 完全揹包 多重揹包

01揹包 zeroonepack 有n件物品和乙個容量為v的揹包,每種物品均只有一件。第i件物品的費用是c i 價值是w i 求解將哪些物品裝入揹包可使價值總和最大。include include includeusing namespace std const int n 1000 10 int ...

01揹包 完全揹包 多重揹包

01揹包 zeroonepack 有n件物品和乙個容量為v的揹包。每種物品均只有一件 第i件物品的費用是c i 價值是w i 求解將哪些物品裝入揹包可使價值總和最大。完全揹包 completepack 有n種物品和乙個容量為v的揹包,每種物品都有無限件可用。第i種物品的費用是c i 價值是w i 求...