一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情。
这是九种背包问题之一,分组背包,做法其实和多重背包相似,只需多做一部预处理。
题目
思路
分组背包,每一类物品都能拿的个数都不尽相同,这里有的物品还有无限个,但是我们可以通过一个背包全部塞满这一类物品,看能塞多少个,从而认为这个物品就只有这么多个。完成这个步骤后,其实题目就变成了多重背包。下一步我们需要做的就是将多重背包转换成01背包。这里说明一个可以极大降低用时的优化方法。任何一个自然数都可以由1,2,4,8...等2的次方数和一个数构成。比如说9,就可以分成1,2,4,2, 1,2,4都是2的次方,最后一个2,是9减去1,2,4所剩余的数。再举个例子,就是19,可以分成1,2,4,8,4,最后的4也是剩下来的数字。而每一类物品都可以这样去分,假设一个价值为4的物品体积是3,它共有8个,我们就可以利用上述所说的方法去将它化为01背包,8个这样的物品可以分为1个这样物品和在一起,2个这样物品合在一起,4个这样物品合在一起,2个这样物品合在一起,这样就有了四个全新物品。价值为4,体积3;价值为8,体积6;价值为16,体积12;价值为8,体积为6。这样做的理由是什么呢?8个这样的物体,我们可以取8种不同情况,第一种就是取一个,那我们直接取上面所化的四个全新物品中的价值为4,体积为3;第二种取两个,就取价值为8,体积为6;第三种取三个,就取价值为8,体积为6,价值为4,体积为3。剩下的五种情况都是可以用上述四个全新物品来构成的。现在多重背包又转换成了01背包,最后按照01背包的做法就可完成这道题目了。
代码
#include <bits/stdc++.h>
using namespace std;
int n,m;
int w[2020],v[2020],s[2020];
int w1[1000010],v1[1000010];
int dp[2020];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++){//化为多重背包
cin>>w[i]>>v[i]>>s[i];
if(s[i]==0){
s[i]=m/w[i];
}
}
int cnt=0;
int j=1;
for(int i=1;i<=n;i++){//化为01背包
j=1;
for(int k=1;j<=s[i];j=1<<k,k++){
w1[++cnt]=w[i]*j;
v1[cnt]=v[i]*j;
s[i]=s[i]-j;
}
if(s[i]){
w1[++cnt]=w[i]*s[i];
v1[cnt]=v[i]*s[i];
}
}
for(int i=1;i<=cnt;i++){
for(int k=m;k>=w1[i];k--){//必须从后往前遍历,不然就不是01背包了,因为从前往后遍历会多次取一个只能取一次的物品
dp[k]=max(dp[k-w1[i]]+v1[i],dp[k]);
}
}
cout<<dp[m];
return 0;
}