题目描述:
给出N个物品(一个物品可以选择0~a个),背包最大承重为M,每个物品有一个重量w,一个价值v,一个个数a。如何选择才能在重量不超过M的情况下,使选择的物品的价值总和最大。
输入格式:
第一行:是两个正整数N,M。
第2 ~ (n + 1)行:每行三个正整数w,v和a。
输出格式:
一个整数,为最大价值总和是多少。
输入样例:
4 20
9 3 3
9 5 1
4 9 2
1 8 3
输出样例:
47
我们发现多重背包和零一背包十分相像,只不过多重背包中,每个物品可以选0 ~ a个。这会让我们想到,直接把第i个物品,拆成a[i]个重量都为w[i],价格也都为v[i]的物品不就行了吗,接下来一个经典的零一背包就可以搞定了。
注:如果你还没学过零一背包,可以去此网址先学习一下https://blog.csdn.net/SkeletonKing233/article/details/94893608
(PS:NR是指物品个数的上限,MR是指重量的上限)
# include
# include
# include
# include
# include
using namespace std;
# define FOR(i, a, b) for(int i = a; i <= b; i++)
# define _FOR(i, a, b) for(int i = a; i >= b; i--)
const int NR = 100000, MR = 100000000;
int n, m;
int v[NR + 10], w[NR + 10];
int dp[MR + 10];
int main()
{
int T;
cin >> T >> m;
FOR(i, 1, T){
int x, y, z;
cin >> x >> y >> z;
FOR(j, 1, z){
w[++n] = x;
v[n] = y;
}
}
FOR(i, 1, n)
_FOR(j, m, w[i])
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
cout << dp[m] << endl;
return 0;
}
如果我们按上面的写法,我们会发现时间和空间的占用太大了,这该怎么办呢?让我们来一起分析一下。
一个物品i,原本只需讨论个数为0 ~ a[i],这(a[i] + 1)种情况。可现在我们拆成了a[i]完全一样的物品,按照零一背包dp,就分析了2 ^ a[i]种情况,而其中许多情况是重复的(例如选第一个、不选第二个和选第二个、不选第一个是一样的)。这样就大大消耗了空间和时间。
那么现在我们就该想了,如何才能让方案尽量少的重复呢?拆成这么多个物品必要吗?这时我们又会想如何用几个整数表示出所有的0 ~ a[i]个数呢?我们发现如果拿{1, 2, 4, 8, ………,2 ^ p,a[i] - 前面所有的数}就可以表示出所有的从0 ~ a[i]的整数了!(5 = 4 + 1, 19 = 16 + 2 + 1 ……),那么我们就只用把一个物品,拆成p或(p + 1)个物品就行了。这样就大大减少了空间与时间的消耗。
# include
# include
# include
# include
# include
using namespace std;
# define FOR(i, a, b) for(int i = a; i <= b; i++)
# define _FOR(i, a, b) for(int i = a; i >= b; i--)
const int NR = 100000, MR = 100000000;
int n, m;
int v[NR + 10], w[NR + 10];
int dp[MR + 10];
int main()
{
int T;
cin >> T >> m;
while(T--){
int x, y, z;
cin >> x >> y >> z;
for(int i = 1; i <= z; i *= 2){
w[++n] = i * x;
v[n] = i * y;
z -= i;
}
if(z != 0){
w[++n] = z * x;
v[n] = z * y;
}
}
FOR(i, 1, n)
_FOR(j, m, w[i])
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
cout << dp[m] << endl;
return 0;
}
God Bless You For Ever!