You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

112 lines
4.0 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##[$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$背包。
<font color='blue' size=4><b>优化思路:</b></font>
比如我们从一个货车搬百事可乐的易拉罐(因为我爱喝不健康的快乐水~),如果存在$200$个易拉罐,小超市本次要的数量为一个小于$200$的数字$n$,搬的策略是什么呢?
$A$、一个一个搬,直到$n$为止。
$B$、在出厂前打成$1$个一箱,$2$个一箱,$4$个一箱,$8$个一箱,$16$个一箱,$32$个一箱,$64$个一箱,乘下$73$个,不够下一轮的$128$个了,<font color='red'><b>该怎么办呢?剩下的打成$73$个一箱!</b></font>
>为什么要把剩下的$73$个打成一个包呢?不是再分解成$64$,$32$这样的组合呢?这是因为本质是化解为$01$背包,一来这么分解速度最快,二来可以表示原来数量的任何子集。
### 三、一维实现代码 <font color='red' size=4><b>【推荐】</b></font>
```cpp {.line-numbers}
#include <bits/stdc++.h>
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 <bits/stdc++.h>
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;
}
```