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.

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

一、与朴素版本的区别

区别在于数据范围变大了:现在是三个循环数据上限分别是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背包,一来这么分解速度最快,二来可以表示原来数量的任何子集。

三、一维实现代码 【推荐】

#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;
}

四、二维+滚动数组代码

#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;
}