有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N≤1000
0<V≤2000
0<vi,wi,si≤2000
提示:
本题考查多重背包的二进制优化方法。
输入样例
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 <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 2010;
int n, v, f[N];
vector<PII> 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]);
}