目录
原题
(1)不能用完全背包问题的理由
首先我们来题干
乍一看很像完全背包问题,但是这里出现了一个限制,就是物品的数量是给定的
如果现在用完全背包问题的优化思路就会发现
f[i][j]=max(f[i-1][j],f[i-1][j-v]+w ...f[i-1][j-k*v+k*w)
f[i][j-v]=max( f[i-1][j-v] .... f[i-1][j-k*v+k*w) f[i-1][j-(k+1)*v+(k+1)*w)
在这个时候由于数量k是给定的,所以在每次比较的时候都要保证有k项,而如果数量k并未给定,
就有这个问题,现在f[i][j-v]比f[i][j]有一项不相同,所以无法用完全背包问题
(2)基础代码
那我们就只能用01背包去解决 现在对集合的划分就变成了
对于第i类物品是否取k个 那么这时候我们就要加入三重循环
第一层循环i类物品 第二层循环j体积 第三次循环k的个数
int n, m;
int v[N], w[N], s[N];//分别表示体积 价值 数量
int f[N][N];
int main()
{
cin >> n >> m; //表示n类 最大体积m
for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i] >> s[i];
for (int i = 1; i <= n; i ++ )
for (int j = 0; j <= m; j ++ )
for (int k = 0; k <= s[i] && k * v[i] <= j; k ++ )
f[i][j] = max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k);
}
(3)优化思路
现在虽然解决了问题,但用上了三重循环太慢了
我们现在来考虑如何优化
(1)证明用2分组的合理性
我们现在要用一种跟快的方式去表示出k个数
对于一个整数而言 以13为例
它的二进制就是1101
那么它就可以表示为
1000 加上 0100 加上 0000 加上 0001
就是2的三次 加2的二次 加二的0次
所以任何一个正整数就可以表示为
s=1+2+4+.....+2^k +c
1~2的k次 就可以表示1~2^(k+1) -1之间的任何一个数
以200为例
它可以拆分为 200=1+2+4+8+16+32+64+63
1+2+4+8+16+32+64可以表示1~127
63与前者相加以后就可以表示64~200之间任意一个数
两个区域合并以后就可以表示1~200之间任意一个数
所以我们写出如下进行分组的代码
int cnt=0;//标记分到第几组了
for(int i=1;i<=n;i++)
{
int a,b,s;//分别表示第i个物品的体积,价值,数量
scanf("%d%d%d",&a,&b,&s);
int k=1;//k用来表示数量 每次乘上2 表示一组
while(k<=s)
{
cnt++;
v[cnt]=a*k;//第cnt组新的体积与价值
w[cnt]=b*k;
s-=k;//有k数量的i物品已经被分走了
k*=2;
}
//这就是我们举例里面的情况
//200里面127的数量已经被分走了 还剩下63 把剩下的63作为一组
if(s>0)
{
cnt++;
v[cnt]=a*s;
w[cnt]=b*s;
}
}
(2)最后使用01背包优化删去一个维度
n=cnt;//注意现在的n n种物品都已经被分好组了 现在用总组数去更新n
for (int i = 1; i <= n; i++)//n类物品
for (int j = m; j >= v[i]; j--)//最大体积m
f[j] = max(f[j], f[j - v[i]] + w[i]);
总结
由此我们可以看出所谓的01背包问题,就是在更新方式上去做两种判断:选或者不选
所以多重背包就也理解为 对我给了那么多给定数量的i类物品,要求我去得到一个表达式
使其可以表达任意数量的物品,
这时候更新方式就变成了 : 我是否选择用这个表达式去计算数量 是或者否
题目来源acwing
模板来源:闫学灿
链接:https://www.acwing.com/activity/content/code/content/57785/
来源:AcWing