[C++]简单背包


01背包

问题模型
n种物品,每种物品有1个,
放入容量为m的背包中,每个物品有体积 v [ i ] v[i] v[i]和价值 w [ i ] w[i] w[i]
求放入背包的最大价值

基本方法

  • n表示物品总数,m表示背包容量,v[]表示物品体积,w[]表示物品价值
  • f[i][j]表示将前i个物品放入容量为j的背包能获得的最大价值
  • f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − v [ i ] ] + w [ i ] , f [ i − 1 ] [ j ] ) ; f[i][j]=max(f[i-1][j-v[i]]+w[i],f[i-1][j]); f[i][j]=max(f[i1][jv[i]]+w[i],f[i1][j]);
  • 边界: f [ 0 ] [ j ] = 0 f[0][j]=0 f[0][j]=0
  • 解: f [ n ] [ m ] f[n][m] f[n][m]

降维

012<-i
00000
1xxxx
2xxxx
3xxxx
j

参考代码:

for(int i=1;i<=n;i++){
    for(int j=m;j>=v[i];j--){
        if(f[j-v[i]]+w[i]>f[j])
            f[j]=f[j-v[i]]+w[i];
    }
}

完全背包

问题模型
n种物品,每种物品有无数个,
放入容量为m的背包中,每个物品有体积 v [ i ] v[i] v[i]和价值 w [ i ] w[i] w[i]
求放入背包的最大价值

方法1

伪代码:

//边界是f[0][j]=0
for(int i=1;i<=n;i++){
    for(int j=0;j<=m;j++){
        for(int k=0;k<=j/v[i];j++){
            f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+w[i]*k);
        }
    }
}
//解是f[n][m]

时间复杂度很高!

方法2
边界和解都一样
f [ i ] [ j ] f[i][j] f[i][j]的定义还一样
此时考虑不选或者至少选一个:

  • 不选的状态就是 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]
  • 选的话,先选一个再说: f [ i ] [ j − v [ i ] ] + w [ i ] f[i][j-v[i]]+w[i] f[i][jv[i]]+w[i]
  • 第二种情况,如果真的某个物品只选1个会不会出问题呢?
  • 不会的,因为如果真的第i个物品只选一个,则 f [ i ] [ j − v [ i ] ] = f [ i − 1 ] [ j − v [ i ] ] f[i][j-v[i]]=f[i-1][j-v[i]] f[i][jv[i]]=f[i1][jv[i]] f [ i ] [ j ] f[i][j] f[i][j]包含了 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j],不会出问题

综上所述, f [ i ] [ j ] = max ⁡ ( f [ i − 1 ] [ j ] , f [ i ] [ j − v [ i ] ] + w [ i ] ) ; f[i][j]=\max(f[i-1][j],f[i][j-v[i]]+w[i]); f[i][j]=max(f[i1][j],f[i][jv[i]]+w[i]);
伪代码:

for(int i=1;i<=n;i++){
    for(int j=0;j<=m;j++){
        if(j>=v[i])
            f[i][j]=max(f[i-1][j]/*不选*/,f[i][j-v[i]]+w[i]/*选至少一个*/);
        else
            f[i][j]=f[i-1][j];
        }
    }
}

降了一层循环,真NICE!

012<-i
00000
1xxxx
2xxxx
3xxxx
j

降维
研究表格,发现可以降维:
f [ j ] = m a x ( f [ j ] , f [ j − v [ i ] ] + w [ i ] ) ; f[j]=max(f[j],f[j-v[i]]+w[i]); f[j]=max(f[j],f[jv[i]]+w[i]);
参考代码:

for(int i=1;i<=n;i++){
    for(int j=v[i];j<=m;j++){
        f[j]=max(f[j],f[j-v[i]]+w[i]);
    }
}

多重背包

问题模型
n种物品,每种物品有c[i]个,
放入容量为m的背包中,每个物品有体积 v [ i ] v[i] v[i]和价值 w [ i ] w[i] w[i]
求放入背包的最大价值

基本方法
f [ i ] [ j ] f[i][j] f[i][j]的定义不变
c [ i ] c[i] c[i]表示第i个物品有多少个
伪代码:

for(int i=1;i<=n;i++){
    for(int j=0;j<=m;j++){
        for(int k=0;k<=min(c[i],j/v[i]);k++){
            f[i][j]=max(f[i-1][j-k*v[i],k*w[i]);
        }
    }
}

降维

012 < − i <-i <i
00000
1xxxx
2xxxx
3xxxx
j

参考代码:

for(int i=1;i<=n;i++){
    for(int j=m;j>=0;j--){
        for(int k=0;k<=min(c[i],j/v[i]);k++){
            if(f[j-k*v[i]]+k*w[i]>f[j])
                f[j]=f[j-k*v[i]]+k*w[i];
        }
    }
}

解法二——转01背包
第i种物品有c[i]个,可以转化成c[i]中物品,每种物品有 1 1 1
这样将多重背包转成了01背包

参考代码:

for(int i=1;i<=n;i++){
    for(int k=1;k<=c[i];k++){//k从1开始,易错
        for(int j=m;j>=v[i];j--){
            if(f[j-v[i]]+w[i]>f[j])
                f[j]=f[j-v[i]]+w[i];//注意这里是01背包的状态转移方程
        }
    }
}
  • 注意:只能在选同种物品时价值不受物品选的个数的限制时使用

混合背包

有些物品只能选1个,有些物品只能选 c [ i ] c[i] c[i]个,有些物品能选无数个
因为所有的背包的边界定义都一样,所以可以根据具体的数量规定具体的状态转移方程

参考代码:

//c[i]=0表示有无数个
for(int i=1;i<=n;i++){
    for(int j=0;j<=m;j++){
        if(c[i]==1){//01背包
            if(j>=c[i])
                f[i][j]=max(f[i-1][j],f[i-1][j-v[i]]+w[i]);
            else
                f[i][j]=f[i-1][j];
        }
        else if(c[i]==0){//完全背包
            if(j>=c[i])
                f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]);
            else
                f[i][j]=f[i-1][j];
        }
        else{//混合背包
            for(int k=0;k<=min(c[i],j/v[i]);k++)
                f[i][j]=max(f[i-1][j],f[i-1][j-k*v[i]]+k*w[i]);
        }
    }
}

降维也要根据不同的背包问题,选择不同的方式

//c[i]=0表示有无数个
for(int i=1;i<=n;i++){
    if(c[i]==1){//01背包
        for(int j=m;j>=v[i];j--)
            f[j]=max(f[j],f[j-v[i]]+w[i]);
    }
    else if(p[i]==0){//完全背包
        for(int j=v[i];j<=m;j++)
            f[j]=max(f[j],f[j-v[i]]+w[i]);
    }
    else{
        for(int j=m;j>=0;j--){//多重背包
            for(int k=0;k<=min(c[i],j/v[i]);k++)
                f[j]=max(f[j],f[j-k*c[i]]+k*w[i]);
        }
    }
}

第二种方式
其实,因为背包容量最大也只能是m,所以理论上能选无数个的物品最多也只能选 m / v [ i ] m/v[i] m/v[i]
01背包可以看成 c [ i ] = 1 c[i]=1 c[i]=1的多重背包
这样,我们可以将三种背包都变成多重背包
模板代码略


恰填满背包的方案总数

  • 对于一个给定了背包容量、物品费用、物品间相互关系(分组、依赖)的背包问题,除了再给定每个物品的价值后求可得到的最大价值外,还可以得到装满背包或将背包装至某一指定容量的方案总数
  • 对于这类问题,一般只需要将状态转移方程中的max改成sum,再将边界稍作修改即可

基本方法
f[i][j]表示将前i件物品恰填满容量为j的背包的方案总数

  • 01背包的状态转移方程是 f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − v [ i ] ] ; f[i][j]=f[i-1][j]+f[i-1][j-v[i]]; f[i][j]=f[i1][j]+f[i1][jv[i]];
  • 完全背包的状态转移方程是 f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i ] [ j − v [ i ] ] ; f[i][j]=f[i-1][j]+f[i][j-v[i]]; f[i][j]=f[i1][j]+f[i][jv[i]];
  • 多重背包的状态转移方程是 f [ i ] [ j ] = ∑ k = 0 min ⁡ ( c [ i ] , j / v [ i ] ) f [ i − k ⋅ v [ i ] ] ; f[i][j]=\sum_{k=0}^{\min(c[i],j/v[i])}f[i-k\cdot v[i]]; f[i][j]=k=0min(c[i],j/v[i])f[ikv[i]];
  • 边界: f [ 0 ] [ 0 ] = 1 f[0][0]=1 f[0][0]=1

思考
  • 在01背包达到最优解时,输出最优解中所有选中物品的编号

参考代码:

#include <iostream>
#include <cstdio>
#define MAXN 10010
#define MAXM 10010
//分开定义MAXN和MAXM表示这个数组的大小跟n和m中的哪一个有关 
using namespace std;
int n,m,v[MAXN],w[MAXN],f[MAXM];
bool g[MAXN][MAXM];
void DFS(int x,int y){
    if(x==0)
    return;
    if(g[x][y]){
        DFS(x-1,y-v[x]);
        printf("%d ",x);
    }
    else{
        DFS(x-1,y);
    }
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;i++)
    scanf("%d %d",&v[i],&w[i]);
    for(int i=1;i<=n;i++){
        for(int j=m;j>=v[i];j--){//降维 
            if(f[j-v[i]]+w[i]>f[j]){
                f[j]=f[j-v[i]]+w[i];
                g[i][j]=true;
            }
        }
    }
    printf("物品最大价值:%d\n选的物品:",f[m]);
    DFS(n,m);//挨个输出物品 
    return 0;
}

恰填满背包的最小物品数

  • 对于一个给定了背包容量、物品费用、物品间相互关系(分组、依赖)的背包问题,除了再给定每个物品的价值后求可得到的最大价值外,还可以得到装满背包或将背包装至某一指定容量的最小物品数量

基本方法
f[i][j]表示将前i件物品恰填满容量为j的背包的最小物品数

  • 01背包的状态转移方程是 f [ i ] [ j ] = min ⁡ ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − v [ i ] ] + 1 ) ; f[i][j]=\min(f[i-1][j],f[i-1][j-v[i]]+1); f[i][j]=min(f[i1][j],f[i1][jv[i]]+1);
  • 完全背包的状态转移方程是 f [ i ] [ j ] = min ⁡ ( f [ i − 1 ] [ j ] , f [ i ] [ j − v [ i ] ] + 1 ) ; f[i][j]=\min(f[i-1][j],f[i][j-v[i]]+1); f[i][j]=min(f[i1][j],f[i][jv[i]]+1);
  • 多重背包的状态转移方程是 f [ i ] [ j ] = min ⁡ ( f [ i ] [ j ] , f [ i − 1 ] [ j − k × v [ i ] ] + k ) ; f[i][j]=\min(f[i][j],f[i-1][j-k \times v[i]]+k); f[i][j]=min(f[i][j],f[i1][jk×v[i]]+k);其中 0 ≤ k ≤ m i n ( c [ i ] , j / v [ i ] ) 0 \le k \le min(c[i],j/v[i]) 0kmin(c[i],j/v[i])
  • 边界: f [ 0 ] [ i ] = 0 x 3 f 3 f 3 f 3 f , f [ 0 ] [ 0 ] = 1 f[0][i]=0x3f3f3f3f,f[0][0]=1 f[0][i]=0x3f3f3f3f,f[0][0]=1

  • 13
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值