多重揹包
多重揹包:每件物品不止一個,並且有個數限制的揹包問題,注意和完全揹包區分。
首先確定狀態\(f(i, j)表示從前i個物品中選擇,並且總體積<= j的所有選法的集合,儲存最大價值屬性。\)
然後對\(f(i, j)進行集合劃分\)
此處出現了和多重揹包之間的差別,完全揹包可以對i號物品的選擇情況無限劃分下去,直到k * v[i] > j
但是多重揹包對i號物品的列舉最多隻能到s[i]。
暴力寫法的時間複雜度為\(O(nms)\)
當n, m, s[i]的範圍比較小的時候可以寫
#include<iostream> using namespace std; const int N = 110; int n, m; int f[N][N]; int w[N], v[N], s[N]; int main(){ cin >> n >> m; for(int i = 1; i <= n; i ++) cin >> v[i] >> w[i] >> s[i]; for(int i = 1; i <= n; i ++) for(int j = 1; j <= m; j ++) for(int k = 0; k <= s[i] && k * v[i] <= j; k ++) f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]); cout << f[n][m]; return 0; }
當\(n * m * s > 10^9\)時暴力超時,此時用二進位制優化方法,優化複雜度到\(O(nmlog{s})\)
證明數列\(1, 2, 4, 8, 16, 32, ... , 2^k\)可以表示\(1 \sim 2^{k + 1} - 1\)的所有數
從二進位制來看:\(1, 2, 4, 8, 16, 32, ... , 2^k\)分別是\(k+1\)位中的從低向高的第1~ k + 1位置1的結果,
顯然,000...1~111...1都可以用這些數中的一些數的和來表示(每個數只用一次)
下面接著證明\(1, 2, 4, 8, 16, 32, ... , 2^k, C(C<2^{k + 1})\)
首先\(1, 2, 4, 8, 16, 32, ... , 2^k\)可以表示\(1 \sim 2^{k + 1} - 1\),由於添加了一個C,那麼還可以表示\(1 + C \sim 2^{k + 1} - 1 + C\),由於\(C<2^{k+1} => C+1 <= 2^{k + 1}\)
所以\([1,2^{k + 1} - 1]\cup[C+ 1, 2^{k + 1} - 1 + C]=[1,2^{k + 1} - 1 + C]\)
由於任何數都可以改寫成2^t + H(H < 2^t)的形式,所以區間右端點可以是任何整數
從而得到了多重揹包的二進位制優化方法
將每一種商品的數量按照以上以上數列的形式劃分成新的商品轉化成新的01揹包問題
比如1號商品有100個,那麼劃分方法是1、2、4、8、16、32、37
所以1號商品被換成了5個單獨的商品
時間複雜度:
把n類商品每類商品有si個的多重揹包問題,轉換為了商品數有\(nlogs\)的01揹包問題,複雜度\(O(nmlogs)\)
#include<iostream>
using namespace std;
const int N = 11010;
int v[N], w[N];
int f[N];
int n, m;
int cnt;
int main(){
cin >> n >> m;
while(n --){
int a, b, c; // v, w, s
cin >> a >> b >> c;
int k = 1;
while(k <= c){
cnt ++;
v[cnt] = k * a;
w[cnt] = k * b;
c -= k;
k <<= 1;
}
if(c > 0){
cnt ++;
v[cnt] = c * a;
w[cnt] = c * b;
}
}
for(int i = 1; i <= cnt; i ++)
for(int j = m; j >= v[i]; j --)
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m];
return 0;
}