4.多重背包问题 II

 当尝试用和完全背包问题相似的思路试图来优化的时候,就会发现优化不了

 用二进制优化

假设我们这件物品一共有1023个

我们真的需要从0到1到2一直枚举到1023吗

有没有一种更高效的方式来枚举呢

可以的

我们把若干个第i件物品打包在一起

打包成2的整次幂的形式

比如说10组

1, 2, 4, 8, ...,512

把第i个物品打包成10组

每一组最多只能选1次

2 ^ 10 = 1024

那么我们是否能用这10组拼凑出来从0到1023中的任何一个数呢

只用前1组,可以拼出0 ~ 1

只用前2组,可以拼出0 ~ 3

只用前3组,可以拼出0 ~ 7

只用前4组,可以拼出0 ~ 15

所以用前10组,可以凑出0 ~ 1023

然后每一个打包起来的第i个物品,可以看成是01背包中的一个物品,因为只可以选1次

就是说我们用10个新的物品,来表示我们的第i个物品

然后我们枚举这10个新的物品选或不选,就可以拼凑出来第i个物品的所有方案

原来需要枚举1024次,现在只需要10次

就是把1024转化为log 1024 = 10

 所以对于一个一般情况下的s

k是使得前面所有项相加之和小于等于s的最大的整次幂。

 这个k是最大的一个k使得1+2+4+8+...+2^k <= s

所以如果给我们第i个物品的数量是s的话

那么我们就可以把它拆成log s(向下取整)个组新的物品

新的物品每个最多只能用一次

所以我们先把所有的物品拆分,再对所有新出来的问题做一遍01背包

原来的三层循环时间复杂度是 n * v * s

现在是n * log s个物品的01背包问题

等于 n * log s * v = n * v * log s

所以对于本题就是1000 * 2000 * log 2000 = 2 * 10^6 * 11 = 2 * 10 ^ 7

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int N = 11010;
 4 //物品个数最多是1000 * log 2000
 5 //log上取整 
 6 int v[N], w[N];
 7 int dp[N];
 8 int main() {
 9     int n, m;
10     cin >> n >> m;
11     int cnt = 0; //新的物品的编号 
12     for (int i = 1; i <= n; i++) {
13         int a, b, s;
14         //读入当前物品的体积,价值,数量 
15         cin >> a >> b >> s;
16         int k = 1; //从1开始分 
17         while (k <= s) { //只要k<=s就可以分,每次把k个第i个物品打包在一起 
18             cnt++; //编号++ 
19             v[cnt] = a * k; //新物品的体积 
20             w[cnt] = b * k; //新物品的价值 
21             s -= k; // 
22             k *= 2;
23         }
24         if (s > 0) { //这就是剩下的c 
25             cnt++;
26             v[cnt] = a * s;
27             w[cnt] = b * s;
28         }
29     }
30     n = cnt;
31     for (int i = 1; i <= n; i++) {
32         for (int j = m; j >= v[i]; j--) {
33             dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
34         }
35     }
36     cout << dp[m] << endl;
37     return 0;
38 }

 

posted @ 2020-07-02 15:07  kyk333  阅读(177)  评论(0)    收藏  举报