一大波模板正在靠近
1.01背包
问题:有n件物品和一个容量为v的背包,第i件物品的费用(即体积)是w[i],价值是v[i],求解将哪些物品装入背包可使这些物品的费用和不超过背包容量,且价值总和最大。
动态转移方程为f[j]=max(f[j],f[j-w[i]]+v[i]),注意关于背包容量要倒着循环,来保证每个物品只选一次
//二维 for(int i=1;i<=n;i++) for(int j=m;j>=0;j--) f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+v[i]); cout<<f[n][m];
//一维 for(int i=1;i<=n;i++) for(int j=m;j>=0;j--) f[j]=max(f[j],f[j-w[i]]); cout<<f[m];
2.完全背包
问题:有n种物品和一个容量为v的背包,每种物品都有无限件可用,第i种物品的费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
动态转移方程为f[j]=max(f[j],f[j-w[i]]+v[i]),完全背包问题与01背包问题只有背包容量的循环次序不同
/*完全背包(无限件可用)*/ for(int i=1;i<=n;i++) for(int j=w[i];j<=m;j++) f[j]=max(f[j],f[j-w[i]]+v[i]);
3.多重背包
问题:有n种物品和一个容量为v的背包,第i种物品最多有s[i]件可用,每件的费用是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
//朴素算法 for(int i=1;i<=n;i++) for(int j=m;j>=0;j--) for(int k=0;k<=s[i];k++) { if(j-w[i]*k<0)break; f[j]=max(f[j],f[j-w[i]*k]+v[i]*k); } cout<<f[m];
//二进制优化,转换为01背包 int n1=0; for(int i=1;i<=n;i++) { cin>>vv>>ww>>s; int t=1; while(s>=t) { w[++n1]=ww*t; v[n1]=vv*t; s-=t; t*=2; } w[++n1]=ww*s; v[n1]=vv*s; } for(int i=1;i<=n1;i++) for(int j=m;j>=w[i];j--) f[j]=max(f[j],f[j-w[i]]+v[i]); cout<<f[m];
4.混合三种背包问题
问题:有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包),该怎么求解呢?
//朴素 for(int i=1;i<=n;i++) { if(s[i]==-1)//完全背包 { for(int j=w[i];j<=m;j++) f[j]=max(f[j],f[j-w[i]]+v[i]); } else { for(int k=1;k<=s[i];k++)//循环件数 for(int j=m;j>=w[i];j--) f[j]=max(f[j],f[k-w[i]]+v[i]); } } cout<<f[m];
//二进制优化 for(int i=1;i<=n;i++) { int ww,vv,ss; int t=1; cin>>ww>>vv>>ss; if(ss==-1) {w[++n1]=ww;v[n1]=vv;s[n1]=-1;continue;} if(ss==1) {w[++n1]=ww;v[n1]=vv;s[n1]=1;continue;} if(ss>1)//多重背包 { int t=1; while(ss>=t) { w[++n1]=ww*t; v[n1]=vv*t; s[n1]=1; ss-=t; t*=2; } if(ss>0){ w[++n1]=ww*ss; v[n1]=vv*ss; s[n1]=1; } continue; } } for(int i=1;i<=n1;i++) { if(s[i]==-1)//完全背包 { for(int j=w[i];j<=m;j++) f[j]=max(f[j],f[j-w[i]]+v[i]); } else { for(int j=m;j>=w[i];j--) f[j]=max(f[j],f[j-w[i]]+v[i]); } } cout<<f[m];
5.二维费用背包
问题:二维费用的背包问题是指对于每件物品,具有两种不同的费用,选择这件物品必须同时付出这两种代价,对于每种代价都有一个可付出的最大值(背包容量),求选择物品可以得到最大的价值。设第i件物品所需的两种代价分别为w1[i]和w2[i],两种代价可付出的最大值(两种背包容量)分别为V和U,物品的价值为v[i]。
状态转移方程为:
f[i][j][k]=max(f[i-1][j][k],f[i-1][j-w1[i]][k-w2[i]]+v[i]);
/*二维费用的背包问题(与01背包相似)*/ for(int i=1;i<=n;i++) for(int j=m1;j>=w1[i];j--) for(int k=m2;k>=w2[i];k--) f[j][k]=max(f[j-w1[i]][k-w2[i]],f[i][j]); cout<<f[m1][m2];
6.分组背包
问题:有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
算法:
这个问题变成了每组物品有若干种策略:是选择本组的某一件,还是一件都不选。也就是说设f[k][v]表示前k组物品花费费用v能取得的最大权值,则有:
f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于组k}
使用一维数组的伪代码如下:
for 所有的组k//循环分组
for v=V..0//容量限制
for 所有的i属于组k//组内物品
f[v]=max{f[v],f[v-c[i]]+w[i]}
/*分组的背包问题*/ for(int i=1;i<=n;i++) { int p; cin>>w[i]>>v[i]>>p; a[p][++a[p][0]]=i; } for(int k=1;k<=t;k++) for(int j=m;j>=0;j--) for(int i=1;i<=a[k][0];i++) { int tmp=a[k][i];//tmp代表k组中的第i个物品在所有物品中的序号 f[j]=max(f[j],f[j-w[tmp]]+v[tmp]); } cout<<f[m];
7.背包问题的方案总数
对于一个给定了背包容量、物品费用的背包问题,除了再给定每个物品的价值后求可得到的最大价值外,还可以得到装满背包或将背包装至某一指定容量的方案总数。对于这类改变问法的问题,一般只需将状态转移方程中的max改成sum即可。例如若每件物品均是0-1背包中的物品,转移方程即为f[j]=sum{f[j],f[j-w[i]]+v[i]},即f[j]+=f[j-w[i]],初始条件f[0]=1。
事实上,这样做可行的原因在于状态转移方程已经考察了所有可能的背包组成方案.
/*背包问题的方案总数*/ //01背包求方案总数 for(int i=1;i<=n;i++) for(int j=m;j>=w[i];j++) f[j]+=f[j-w[i]]; cout<<f[m];