##[$AcWing$ $5$. 多重背包问题 II](https://www.acwing.com/problem/content/description/5/) ### 一、与朴素版本的区别 区别在于数据范围变大了:现在是三个循环数据上限分别是$1000$(物品种数),$2000$(背包容积),第$i$种物品的体积、价值和数量的上限也是$2000$,原来的每个数字上限都是$100$! 三重循环的话,计算次数就是 $1000 * 2000 * 2000=4000000000=4 * 1e9 =40$亿次 $C++$一秒可以算$1e8$次,就是$1$亿次,$40$亿肯定会超时! ### 二、二进制优化 朴素多重背包做法的本质:将有数量限制的相同物品看成多个不同的$0-1$背包。 优化思路: 比如我们从一个货车搬百事可乐的易拉罐(因为我爱喝不健康的快乐水~),如果存在$200$个易拉罐,小超市本次要的数量为一个小于$200$的数字$n$,搬的策略是什么呢? $A$、一个一个搬,直到$n$为止。 $B$、在出厂前打成$1$个一箱,$2$个一箱,$4$个一箱,$8$个一箱,$16$个一箱,$32$个一箱,$64$个一箱,乘下$73$个,不够下一轮的$128$个了,该怎么办呢?剩下的打成$73$个一箱! >为什么要把剩下的$73$个打成一个包呢?不是再分解成$64$,$32$这样的组合呢?这是因为本质是化解为$01$背包,一来这么分解速度最快,二来可以表示原来数量的任何子集。 ### 三、一维实现代码 【推荐】 ```cpp {.line-numbers} #include using namespace std; const int N = 1010; // 个数上限 const int M = 2010; // 体积上限 int n, m, idx; int f[M]; /* Q:为什么是N*12? A:本题中v_i<=2000,因为c数组是装的打包后的物品集合,每类物品按二进制思想,2000最多可以打log2(2000)+1个包,即 10.96578+1=12个足够, 同时,共N类物品,所以最大值是N*12。 如果题目要求v_i<=INT_MAX,那么就是log2(INT_MAX)=31,开31个足够,因为31是准确的数字,不需要再上取整。 为保险起见,可以不用计算数组上限,直接N*32搞定! */ struct Node { int w, v; } c[N * 12]; int main() { cin >> n >> m; // 多重背包的经典二进制打包办法 for (int i = 1; i <= n; i++) { int v, w, s; cin >> v >> w >> s; for (int j = 1; j <= s; j *= 2) { // 1,2,4,8,16,32,64,128,...打包 c[++idx] = {j * w, j * v}; s -= j; } // 不够下一个2^n时,独立成包 if (s) c[++idx] = {s * w, s * v}; } // 按01背包跑 for (int i = 1; i <= idx; i++) for (int j = m; j >= c[i].v; j--) // 倒序 f[j] = max(f[j], f[j - c[i].v] + c[i].w); // 输出 printf("%d\n", f[m]); return 0; } ``` ### 四、二维+滚动数组代码 ```cpp {.line-numbers} #include using namespace std; const int N = 1010; // 个数上限 const int M = 2010; // 体积上限 int n, m, idx; // 无法使用二维数组,原因是因为分拆后N*31*M=31*1010*2010太大了,MLE了 // 所以,需要使用滚动数组进行优化一下,思想还是二维的 int f[2][M]; struct Node { int w, v; } c[N * 31]; int main() { cin >> n >> m; for (int i = 1; i <= n; i++) { int v, w, s; cin >> v >> w >> s; for (int j = 1; j <= s; j *= 2) { // 1,2,4,8,16,32,64,128,...打包 c[++idx] = {j * w, j * v}; s -= j; } // 不够下一个2^n时,独立成包 if (s) c[++idx] = {s * w, s * v}; } // 按01背包跑就可以啦 for (int i = 1; i <= idx; i++) for (int j = 1; j <= m; j++) { f[i & 1][j] = f[i - 1 & 1][j]; if (j >= c[i].v) f[i & 1][j] = max(f[i & 1][j], f[i - 1 & 1][j - c[i].v] + c[i].w); } // 输出 printf("%d\n", f[idx & 1][m]); return 0; } ```