acwing-5. 多重背包问题 II


有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

输出格式

输出一个整数,表示最大价值。

数据范围

0 0 0

提示:

本题考查多重背包的二进制优化方法。

输入样例

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:

10

方法一:

与上一题的多重背包问题题干相同,数据增加了一个数量级,说明要进行优化,否则会TLE。这里的做法是转化成01背包问题

对于同一类物品,只要能列举出其[0, si]中的所有情况就可以了。假如转化为朴素01背包(在说了要用01背包来做之后,这个分法是最直接能想到的),做法是同类物品拆成多个,例如s=10,分成1 1 1 1 1 1 1 1 1 1,通过其选与不选来表示出[0, si]中的所有情况,但此时复杂度为O(n * v * s),复杂度数量级达到109。考虑二进制表示的方式,即用log(s)位数,就可以表示出[0, si]内任何数,例如s=10,只需要将物品分成1 2 4 3,通过对这几份物品进行选与不选,就可以表示出[0, 10]内的所有数字,再例如s=20,可以分成1 2 4 8 5。因此二进制表示的方式将复杂度优化为O(n * v * log(s))

值得学习的地方在于这种二进制表示法,本质上可以用其它分法,只要能表示出[0, s]即可,但这种分法符合直觉,并且log的复杂度也会很低

#include 

using namespace std;


typedef pair PII;
const int N = 2010;
int n, v, f[N];
vector obj;

// 转化为朴素01背包:同种多件物品拆解成1 1 1 1...,复杂度O(N)
// 转化为二进制01背包:同种多件物品拆解成1 2 4... 余数,复杂度O(logN)

// 本质都是01背包,但拆解的方法可以表示[0, si]内的所有数字即可
// by yxc

int main() {
    int a, b, s;

    scanf("%d%d", &n ,&v);
    for (int i = 1; i <= n; ++i) {
        scanf("%d%d%d", &a, &b, &s);
        for (int j = 1; j <= s; j *= 2) {
            s -= j;
            obj.emplace_back(a*j, b*j);
        }
        if (s) obj.emplace_back(a*s, b*s);
    }

    for (auto o : obj) {
        for (int i = v; i >= o.first; i--) {
            f[i] = max(f[i], o.second + f[i-o.first]);
        }
    }

    printf("%d", f[v]);
}