问题
如果将01背包、完全背包、多重背包混合起来。也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢?
01背包与完全背包的混合
考虑到在01背包和完全背包中最后给出的伪代码只有一处不同,故如果只有两类物品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是O(VN)。伪代码如下:
for i=1..N
if 第i件物品是01背包
for v=V..0
f[v]=max{f[v],f[v-c[i]]+w[i]};
else if 第i件物品是完全背包
for v=0..V
f[v]=max{f[v],f[v-c[i]]+w[i]};
再加上多重背包
如果再加上有的物品最多可以取有限次,那么原则上也可以给出O(VN)的解法:遇到多重背包类型的物品用单调队列解即可。但如果不考虑超过NOIP范围的算法的话,用多重背包中将每个这类物品分成O(log n[i])个01背包的物品的方法也已经很优了。
cin>>x>>y>>s;
while (s>=t){
weight[++n1]=x*t;
value[n1]=y*t;
s-=t;
t*=2;
}
weight[++n1]=x*s;
value[n1]=y*s; //把s以2的指数分堆:1,2,4,…,2^(k-1),s-2^k+1,
小结
有人说,困难的题目都是由简单的题目叠加而来的。这句话是否公理暂且存之不论,但它在本讲中已经得到了充分的体现。本来01背包、完全背包、多重背包都不是什么难题,但将它们简单地组合起来以后就得到了这样一道一定能吓倒不少人的题目。但只要基础扎实,领会三种基本背包问题的思想,就可以做到把困难的题目拆分成简单的题目来解决
旅行者有一个容量为V公斤的背包
n :物品种数
w :物品重量
c :物品价值
p :物品可以拿取的数目(0表示可取无穷多件)
输入样例:
10 3
2 1 0
3 3 1
4 5 4
输出样例:
11
//完全背包,同种物件多次拿取
for(int j=w[i];j<=m;j++)//从小到大的叠加多次选择
f[j]=max( f[j],f[j-w[i]]+c[i] );
for(int j=1;j<=p[i];j++) //01背包和多重背包,拿的次数
for(int k=m;k>=w[i];k--)//从大到小的空间的每次选一个,从后面值结合现值选出最大值
f[k]=max( f[k],f[k-w[i]]+c[i] );
多重背包 变 01背包,完全背包不变
#include<cstdio>
#include<iostream>
using namespace std;
int m,n;
int w[31],c[31],p[31];
int f[201];
int max(int x,int y)
{
if(x<y)
return y;
else
return x;
}
int main()
{
scanf("%d%d",&m,&n);
int n1=0;
int x,y,s;
for(int i=1; i<=n; i++)//都变成了01背包问题和完全背包问题
{
cin>>x>>y>>s;
if(s>1)
{
int t=1;
while (s>=t)
{
w[++n1]=x*t;
c[n1]=y*t;
p[n1]=1;
s-=t;
t*=2;
}
w[++n1]=x*s;
c[n1]=y*s;
p[n1]=1;
}
else
{
w[++n1]=x;
c[n1]=y;
p[n1]=s;
}
}
for(int i=1; i<=n1; i++)
{
if(p[i]==0) //完全背包
{
for(int j=w[i]; j<=m; j++) //从小到大的叠加多次选择
f[j]=max( f[j],f[j-w[i]]+c[i] );
}
else //01背包
{
//for(int j=1; j<=p[i]; j++) //多重背包
for(int k=m; k>=w[i]; k--) //从大到小的逐一选择,从后面值结合现值选出最大值
f[k]=max( f[k],f[k-w[i]]+c[i] );
}
}
for(int i=1; i<=m; i++)
printf("%d\n",f[i]);
printf("%d",f[m]);
return 0;
}
#include<cstdio>
#include<iostream>
using namespace std;
int m,n;
int w[31],c[31],p[31];
int f[201];
int max(int x,int y)
{
if(x<y)return y;
else return x;
}
int main(){
scanf("%d%d",&m,&n);
for(int i=1;i<=n;i++)
scanf("%d%d%d",&w[i],&c[i],&p[i]);
for(int i=1;i<=n;i++){
if(p[i]==0) //完全背包
{
for(int j=w[i];j<=m;j++)//从小到大的叠加多次选择
f[j]=max( f[j],f[j-w[i]]+c[i] );
}
else
{
for(int j=1;j<=p[i];j++) //01背包和多重背包
for(int k=m;k>=w[i];k--)//从大到小的逐一选择,从后面值结合现值选出最大值
f[k]=max( f[k],f[k-w[i]]+c[i] );
}
}
printf("%d",f[m]);
return 0;
}