# 多重背包问题
每种物品有一个固定取数的上限
- 有 N 种物品和一个容量是 V 的背包。
- 第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
- 求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
- 输出最大价值。
输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
数据范围
0<N,V≤100
0<vi,wi,si≤100
1
2
2
f[i] 表示总体积是 i 的情况下的最大价值;
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, m;
int f[N];
int main(){
cin >> n >> m;
for (int i = 0; i<n; i++){
int v, w, s;
cin >> v >> w >> s;
for (int j = m; j >= 0; j--){
for (int k =1; k<=s && k * v<= j; k++){ // 01背包问题加一层循环
f[j] = max(f[j], f[j - k *v] + k* w);
}
}
}
cout << f[m] << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 多重背包的二进制优化
数据范围
0<N≤1000
0<V≤2000
0<vi,wi,si≤2000
1
2
3
2
3
如果上面数据范围仍按照三维循环去求解,时间复杂度将会达到 2e9,会 TLE
优化方法:通过把物品件数拆为二进制份,转化为 01 背包问题求解
二进制拆分算法:
- 比如 10 个物品,我们可以拆成
1,2,4 和 3(10-1-2-4得到)
,则1,2,4,3
可以组合得到 1 到 10 的所有数字, - 如何证明?
证明:s 分为两部分
1,2,4,8,...,2^k
和s - 2^k
,前半部分,我们可以通过二进制表示证明 1 到 2^k 都可取,剩下部分s - 2^k
的取值范围在[0,2^k)之间,我们可以想象把后半部分移到前面,前半部分移到后面,可以得到后半部分的任意取值也都可以得到
#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
int n, m;
int f[N] = {0};
struct Good {
int v, w;
};
int main(){
cin >> n >> m;
vector<Good> goods;
for (int i = 0; i< n; i++){
int v, w, s;
cin >> v >> w >> s;
for (int k = 1; k <= s; k *= 2){
s -= k;
goods.push_back({v*k, w*k}); // 将多重背包打包成了经过二进制拆分的包裹
}
if (s > 0) goods.push_back({v*s, w*s});
}
for (auto good: goods){
for (int j = m; j >= good.v; j--){
f[j] = max(f[j], f[j - good.v] + good.w);
}
}
cout << f[m] << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32