花了一下午时间拜读了菊苣的《背包九讲》。但还没有勇气挑战A题,先写个笔记热个身。。
1. 01背包问题
1.1 题目
有N件物品和一个容量为V 的背包。放入第i件物品耗费的空间是Ci,得到的价值是Wi。求解将哪些物品装入背包可使价值总和最大。(注意这个问题有两种版本,一个版本是消耗体积恰好为V时得到的最大价值,另一个版本是消耗体积<=V时得到的最大价值。除了1.2实现了两个版本之外,其他均默认第二个版本。)
1.2 最基础的实现
版本一:
//d[i][j]=-1表示这种情况不存在。
memset(d,-1,sizeof(d));
d[0][0]=0;
for(int i=1;i<=n;i++)
for(int j=c[i];j<=V;j++){
d[i][j]=d[i-1][j];
if(d[i-1][j-c[i]]!=-1) d[i][j]=max(d[i][j],d[i-1][j-c[i]]+w[i]);}
if(d[n][V]!=-1)ans=d[n][V];
else printf("Impossilbe.");
版本二:
memset(d,0,sizeof(d));
for(int i=1;i<=n;i++)
for(int j=c[i];j<=V;j++) d[i][j]=max(d[i-1][j],d[i-1][j-c[i]]+w[i]);
ans=d[n][V];
1.3 优化空间复杂度至OV
应用滚动数组。j的枚举应该倒序。
memset(d,0,sizeof(d));
for(int i=1;i<=n;i++)
for(int j=V;j>=c[i];j--)
d[j]=max(d[j],d[j-c[i]]+w[i]);
ans=d[v];
void Zeropack(int* f,int C,int W){
for(int i=V;i>=C;i--) f[i]=max(f[i],f[i-C]+W);
}
1.4 已在1.1中提及
1.5 没有看懂
2.完全背包问题
2.1 题目
有N种物品和一个容量为V 的背包,每种物品都有无限件可用。放入第i种物品的耗费的空间是Ci,得到的价值是Wi。求解:将哪些物品装入背包,可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。
2.2 最基础的实现(空间复杂度NV,时间复杂度NVsigma(V/ci))
memset(d,0,sizeof(0));
for(int i=1;i<=n;i++)
for(int j=V;j>=c[i];j--)
for(int k=0;k*c[i]<=j;k++) d[i][j]=max(d[i-1][j-k*c[i]]+k*w[i],d[i-1][j]);
ans=d[n][V]);
2.3 这个优化就是过滤一批物品,复杂度为N+V
memset(val,-1,sizeof(val));
for(int i=1;i<=n;i++) if(c[i]<=V) val[c[i]]=max(val[c[i]],w[i]);
int st=0;
for(int i=0;i<=V;i++) if(val[i]>=0){ neww[st]=val[i];newc[st]=i;st++;}
//过滤剩下的物品序号为0-st-1,按照占用空间从小到大顺序排序,占用空间为newc[i],价值为neww[i]
2.4 转化为01背包。转化的复杂度为N*sigma log (V/c[i]) 总的复杂度为NV*sigma log (V/c[i])
int st=0;
for(int i=1;i<=n;i++){
int k=V/c[i],maxj=0;
for(int j=0;(1<<j)<=k;j++){
neww[st]=(1<<j)*w[i];
newc[st]=(1<<j)*c[i];
st++;
maxj=j;
}
newc[st]=(k-maxj)*c[i];neww[st]=(k-maxj)*w[i]; st++;
}
2.5 复杂度为NV的做法
memset(d,0,sizeof(d));
for(int i=1;i<=n;i++)
for(int j=c[i];j<=V;j++)
d[j]=max(d[j],d[j-c[i]]+w[i]);
void Complete(int *f,int C,int W){
for(int i=C;i<=V;i++) f[i]=max(f[i],f[i-C]+W);
}
3 多重背包问题
3.1 题目
有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci,价值是Wi。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。
3.2最基础的实现(时间复杂度为NVsigmaMi,空间复杂度为NV)
for(int i=1;i<=n;i++)
for(int j=V;j>=c[i];j--){
int M=j/v[i];
for(int t=0;t<=min(m[i],M);t++)
d[j]=max(d[j],d[j-t*c[i]]+t*w[i]);
}//注意m[i]和M的比较,否则可能越界
3.3 转化为01背包问题
int st=0;
for(int i=1;i<=n;i++){
int maxj=0;
for(int j=0;(1<<j)<=m[i];j++){
neww[st]=(1<<j)*w[i];
newc[st]=(1<<j)*c[i];
st++;
maxj=j;
}
neww[st]=(k[i]-maxj)*w[i];
newc[st]=(k[i]-maxj)*c[i];
st++;
}
void Multiplepack(int* f,int C,int W,int M){
int K=V/c,maxi=0;
M=min(K,M);
for(int i=0;(1<<i)<=M;i++){
Zeropack(f,(1<<i)*C,(1<<i)*W);
maxi=i;
}
Zeorpack(f,(M-(1<<i))*C,(M-(1<<i))*W);
}
3.4 等我看完“男人八题”再来填坑好了,虽然我不是男人。。
4 .混合三种背包问题
4.1 问题
如果将前面1、2、3中的三种背包问题混合起来。也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢?
4.2 01背包与完全背包的混合
考虑到01背包和完全背包中给出的伪代码只有一处不同,故如果只有两类物品:一类物品只能取一次,另一类物品可以取无限次,那么只需在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是VN。flag[i]=0表示只能取一次,flag[i]=1表示能取无限次。
for(int i=1;i<=n;i++){
if(flag[i]==0) Zeropack(f,c[i],w[i]);
else Completepack(f,c[i],w[i]);
}
4.3 再加上多重背包 (m[i]=-1表示可以取无限次,m[i]=0表示只能取一次,其他表示可以取m[i]次。
for(int i=1;i<=n;i++){
if(m[i]==0) Zeropack(f,c[i],w[i]);
else if(m[i]==-1) Completepack(f,c[i],w[i]);
else Multiplepack(f,c[i],w[i],m[i]);
}
5 二维费用的背包问题
5.1 问题
二维费用的背包问题是指:对于每件物品,具有两种不同的空间耗费,选择这件物品必须同时付出这两种代价。对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价一和代价二,第i件物品所需的两种代价分别为Ci和Di。两种代价可付出的最大值(两种背包容量)分别为V 和U。物品的
价值为Wi。
5.2 最基础的方法 时间空间复杂度均为NDV
memset(d,0,sizeof(d));
for(int i=1;i<=n;i++)
for(int j=c[i];j<=V;j++)
for(int k=u[i];k<=D;k++)
d[i][j][k]=max(d[i-1][j][k],d[i-1][j-c[i]][k-u[i]]+w[i]);
空间复杂度DV
memset(d,0,sizeof(d));
for(int i=1;i<=n;i++)
for(int j=V;j>=c[i];j--)
for(int k=D;k>=u[i];k--)
d[j][k]=max(d[j][k],d[j-c[i]][k-u[i]+w[i]);
5.3 就是我们的B题。。当时想到了答案是除掉价格最高的菜后,剩下的菜里能花掉V-5内最多的钱。但是当时卡住了不知道怎么实现。
6 分组的背包问题
6.1 问题
有N件物品和一个容量为V 的背包。第i件物品的费用是Ci,价值是Wi。这些物品被划分为K组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
6.2 时间复杂度不超过V*sigmak[i],空间复杂度为V
//在2.3优化的基础上进行。 k[i]保存的不是初始的k[i],而是优化后的物品个数。每一组内是按照newc从小到大的顺序排序的。
for(int i=1;i<=K;i++)
for(int j=V;j>=0;j--){
for(int t=0;t<k[i];t++){
if(newc[i][t]>j)break;
d[j]=max(d[j],d[j-newc[i][t]]+neww[i][t]);
}
}
7 有依赖的背包问题
7.1 简化的问题
这种背包问题的物品间存在某种“依赖”的关系。也就是说,物品i依赖于物品j,表示若选物品i,则必须选物品j。为了简化起见,我们先设没有某个物品既依赖于别的物品,又被别的物品所依赖;另外,没有某件物品同时依赖多件物品。
7.2 预处理的复杂度为sigma(2的ki次方)
//对第i组的预处理,总共有k[i]个附件,主件的c=c1,w=w1;
memset(val,0,sizeof(val));
for(int i=0;i<(1<<k[i]);i++){
int c=c1,w=w1;
for(int j=0;j<n;j++) if(i&(1<<j)){ c+=c[i];w+=w[i];}
if(val[c[i]]<w[i])val[c[i]]=w[i];
}
if(val[c1]<w1)val[c1]=w1;
int st=0;
for(int i=0;i<=V;i++)if(val[i]){newc[st]=i;neww[st]=val[i];st++ }
}
k[i]=st-1;
7.3 遇到具体问题再填坑。。
8 泛化物品的和
看到scheme有种好亲切的感觉。不过感觉这主要介绍了一种思想,具体问题具体实现。
9 背包问题问法的变化
(待填坑)