前沿:
从最简单的三种背包:01背包,完全背包,多重背包开始,
第四、第五、第六是背包问题的延伸,每个则又区分了01背包,完全背包,多重背包三种情况,
最后的进阶是更高的延伸出几大背包难点
本文目前只总结了这些问题类型,以及对应的核心代码模板,第四往下的问题直接优化为一维代码了
全文代码说明:
n是物体数量,m是背包最大体积,
a[i]是每个物品的体积
b[i]是每个物品的价值
c[i]是每个物品的数量
dp有一维dp[i]也有二维dp[i][j]
文章目录
二维公式 dp[i][j]=max(dp[i-1][j],dp[i-1][j-a[i]]+b[i])
一维公式(从m到a[i]反递) dp[j]=max(dp[j],dp[j-a[i]])
二维公式 dp[i][j]=max(dp[i-1][j],dp[i][j-a[i]]+b[i])
一维公式(从a[i]到m正递) dp[j]=max(dp[j],dp[j-a[i]])
六、不小于背包容量最小价值(和一,二,三的最大区别在于min)
一、01背包(0或1)
二维公式 dp[i][j]=max(dp[i-1][j],dp[i-1][j-a[i]]+b[i])
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(j>=a[i])
dp[i][j]=max(dp[i-1][j],(b[i]+dp[i-1][j-a[i]]))
else
dp[i][j]=dp[i-1][j];
}
cout<<dp[n][m]<<endl;
一维公式(从m到a[i]反递) dp[j]=max(dp[j],dp[j-a[i]])
for(int i=1;i<=n;i++)
for(int j=m;j>=a[i];j--)
dp[j]=max(dp[j],(b[i]+dp[j-a[i]]));
cout<<dp[m]<<endl;
二、完全背包(物体数量无限)
二维公式 dp[i][j]=max(dp[i-1][j],dp[i][j-a[i]]+b[i])
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(j>=a[i])
dp[i][j]=max(dp[i-1][j],(b[i]+dp[i][j-a[i]]))
else
dp[i][j]=dp[i-1][j];
}
cout<<dp[n][m]<<endl;
一维公式(从a[i]到m正递) dp[j]=max(dp[j],dp[j-a[i]])
for(int i=1;i<=n;i++)
for(int j=a[i];j<=m;j--)
dp[j]=max(dp[j],(b[i]+dp[j-a[i]]));
cout<<dp[m]<<endl;
三、多重背包(完全转化为01背包)
1.二进制优化(不能记录每次放的数量)
for(int i=1;i<=n;i++)
{
int a,b,c,s=1;
cin>>a>>b>>c;
while(s<c)
{
v[++len]=a*s; //v是优化后背包体积
w[len]=b*s; //w是优化后背包价值
c-=s; //先减后乘
s*=2;
}
v[++len]=a*c;
w[len]=b*c;
}
for(int i=1;i<=len;i++)
for(int j=m;j>=v[i];j--)
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
cout<<dp[m]<<endl;
2.转化为01背包(可记录每次放的数量)
memset(dp,0,sizeof(dp));
scanf("%d%d",&m,&n);
for(int i=1; i<=n; i++)
scanf("%d%d%d",&a[i],&b[i],&c[i]);
for(int i=1; i<=n; i++){
for(int j=m; j>=a[i]; j--){
for(int k=0;k<=c[i];k++){
if(a[i]*k<=j){
dp[j]=max(dp[j],dp[j-a[i]*k]+b[i]*k);
}
else break;
}
}
}
cout<<dp[m]<<endl;
四、放满的方案数
1.01背包
二维公式 dp[i][j]=dp[i-1][j]+dp[i-1][j-a[i]]
一维公式 dp[j]+=dp[j-a[i]]
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
dp[0]=1;
for(int i=1;i<=n;i++)
for(int j=m;j>=a[i];j--)
dp[j]+=dp[j-a[i]];
cout<<dp[m]<<endl;
2.完全背包
二维公式 dp[i][j]=dp[i-1][j]+dp[i][j-a[i]]
一维公式 dp[j]+=dp[j-a[i]]
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
dp[0]=1;
for(int i=1;i<=n;i++)
for(int j=a[i];j<=m;j++)
dp[j]+=dp[j-a[i]];
cout<<dp[m]<<endl;
3.多重背包(完全按01背包来)
len=0;
for(int i=1;i<=n;i++)
{
int s=1;
while(s<c[i])
{
v[++len]=que[i]*s;
c[i]-=s;
s*=2;
}
v[++len]=que[i]*c[i];
}
//for(int i=1;i<=len;i++)
//cout<<v[i]<<endl;
dp[0]=1;
for(int i=1;i<=len;i++)
for(int j=m;j>=v[i];j--)
dp[j]+=dp[j-v[i]];
cout<<dp[m]<<endl;
五、混合背包(完全和多重背包)
思路:直接放完整代码,遇到时直接更改
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn = 1e3;
int limited[maxn];
int w[maxn],val[maxn];
int f[maxn];
int cnt,v,n;
void init(){
memset(limited,0,sizeof limited);
memset(w,0,sizeof w);
memset(val,0,sizeof val);
cnt = 0;
}
void solve(){
for(int i = 1;i <= cnt;i++){
if(limited[i]){
for(int j = 1;j <= v;j++) if(j >= w[i])
f[j] = max(f[j],f[j-w[i]]+val[i]);
}else{
for(int j = v;j > 0;j--) if(j >= w[i])
f[j] = max(f[j],f[j-w[i]]+val[i]);
}
}
printf("%d\n",f[v]);
}
int main(){
while(~scanf("%d%d",&v,&n)){
int a,b,c;
for(int i = 0;i < n;i++){
scanf("%d%d%d",&a,&b,&c);
if(c == 0){
w[++cnt] = a,val[cnt] = b;
limited[cnt] = 1;
}else{
int t = 1;
while(c >= t){
val[++cnt] = b*t;
w[cnt] = a*t;
c -= t;
t *= 2;
}
if(c){
val[++cnt] = b*c;
w[cnt] = a*c;
}
}
}
solve();
}
return 0;
}
六、不小于背包容量最小价值(和一,二,三的最大区别在于min)
1.01背包
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i]>>b[i];
memset(dp,inf,sizeof(dp));
for(int i=1;i<=n;i++)
for(int j=m;j>=1;j--){
if(j>a[i])
dp[j]=min(dp[j],dp[j-a[i]]+b[i]);
else
dp[j]=min(dp[j],b[i]);
}
cout<<dp[m]<<endl;
2.完全背包
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i]>>b[i];
memset(dp,inf,sizeof(dp));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(j>a[i])
dp[j]=min(dp[j],dp[j-a[i]]+b[i]);
else
dp[j]=min(dp[j],b[i]);
}
cout<<dp[m]<<endl;
3.多重背包(转化为01背包)
七、进阶
1.若求刚好装满时的最大(小)价值
例题: 例题链接
a.完全背包 最小价值 刚好装满容量(属于第六类型中的完全背包)
memset(dp,inf,sizeof(dp));
dp[0]=0; //解决j=a[i]时的dp[0]=0
for(int i=1;i<=n;i++)
for(int j=a[i];j<=m;j++)
dp[j]=min(dp[j],dp[j-a[i]]+b[i]);
//else(j<a[i])
//dp[j]=min(dp[j],b[i]); //去掉该情况因为,j小于a[i]时,
dp[j]必须等于dp[j],传递的是刚好装满的状态,不会用上新的物体
//此时是用上新的物体完全个,一定要装满,要是用0个,就是dp[j],要是用1,2,3……个,
先少一个,要想装满去看dp[j-a[i]]的状态,因为该状态是使用该物体完全个
cout<<dp[m]<<endl;
b.完全背包 最大价值 刚好装满容量(属于第二类型中的完全背包)
memset(dp,-inf,sizeof(dp)); //区别仅在此,不满的情况直接为inf不在传递该状态
dp[0]=0;
for(int i=1;i<=n;i++)
for(int j=a[i];j<=m;j++)
dp[j]=max(dp[j],dp[j-a[i]]+b[i]);
//判断不能达到要求可用 dp[j]<0
(总结:min用inf,max用-inf,若dp[m]==inf(-inf)则不能达到要求)
2.若求满足条件每个物品的件数,则在上面的基础上加记录路径
例题:例题链接
属于:完全装满方案数(第四),加上记录路径
3.背包第k最优解
例题:背包第k最优解