ch4(DP)——背包问题(01背包、完全背包、多重背包、分组背包、混合背包)

1.自然语言描述
① 01背包问题:有n个物品,每个物品有相应的体积vi和价值wi,对于一个物品,要么装入背包,要么不装入;有一个体积为V的背包,怎么装物品才能使背包中物品的价值最大?

② 完全背包问题:有n种物品,每种物品有相应的体积vi和价值wi,对于一种物品可以选任意多个装入背包(即每种物品的数量是无穷大的)有一个体积为V的背包,怎么装物品才能使背包中物品的价值最大?

③ 多重背包问题:有n种物品,每种物品有相应的体积vi和价值wi,第i种物品的数量是si个;有一个体积为V的背包,怎么装物品才能使背包中物品的价值最大?

④ 分组背包问题:有n组物品,每组物品中有si个物品,这si个物品中的每一个都有各自的体积v和价值w,每组物品里最多只能选一个;有一个体积为V的背包,怎么装物品才能使背包中物品的价值最大?

背包问题是学习DP的一个基本模型,以上四种问题之间各有差别,各自的DP策略也是不一样的。

yxc 老师将DP问题描述为:有限集合内求最优解的问题。yxc 老师总结了闫氏DP分析法,这个方法的核心在于从集合的角度思考问题;这是一个思考方式的模板,方法的具体讲解请点这儿!

用闫氏DP分析法来看上述的4个问题:
对于①:状态表示f(i,j)含义是对于前i个物品背包容量为j时价值的最大值,f的属性是最大值;f(i,j)状态的集合划分,分为选第i件物品和不选第i件物品两种状态;对于前一种情况,f(i,j)=f(i-1,j),对于后者,f(i,j)=f(i-1,j-vi)+wi;二者中取最大值f(i,j)=max( f(i-1,j) , f(i-1,j-vi)+wi );得到状态转移方程后,从f(1,0)开始迭代计算得到f(n,v)

对于②:状态表示f(i,j)含义是对于前i种物品背包容量为j时价值的最大值,f的属性是最大值;f(i,j)状态的集合划分,分为选第i种物品的0,1,2,…,k件,f(i,j)=max(f(i-1,j),f(i-1,j-vi)+wi,f(i-1,j-2vi)+2wi,…,f(i-1,j-kvi)+kwi),简化方程,因为f(i,j-vi)=max(f(i-1,j-vi),f(i-1,j-2vi)+wi,…,f(i-1,j-kvi)+(k-1)wi),将后者代入前者得到状态转移方程f(i,j)=max(f(i-1,j),f(i,j-vi)+wi);得到状态转移方程后,从f(1,0)开始迭代计算得到f(n,v)

对于③:状态表示f(i,j)含义是对于前i种物品背包容量为j时价值的最大值,f的属性是最大值;f(i,j)状态的集合划分,分为选第i种物品的0,1,2,…,si件;参考②的推理方法,得出状态转移方程f(i,j)=max(f(i-1,j),f(i-1,j-vi)+wi,f(i-1,j-2vi)+2wi,…,f(i-1,j-sivi)+siwi);得到状态转移方程后,从f(1,0)开始迭代计算得到f(n,v)

对于④:状态表示f(i,j)含义是对于前i组物品背包容量为j时价值的最大值,f的属性是最大值;f(i,j)状态的集合划分,分为选第i组物品的第k件,参考②的推理方法,得出状态转移方程f(i,j)=max(f(i-1,j),f(i-1,j-vk)+wk);得到状态转移方程后,从f(1,0)开始迭代计算得到f(n,v)

上述分析过程得到的状态转移方程要在实际程序中写成合理的形式。


2020.9.6更新
混合背包问题:将01背包、完全背包、多重背包混合在一起;可以通过倍增打包的方式将多重背包问题转化为01背包问题,而完全背包问题和01背包问题的状态转移方程在一维情况下只是枚举的顺序相反,而状态转移方程的代码完全相同,整个问题得到简化。

2.代码描述
题目:Acwing.2 01背包问题题目链接

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int MAXN=1010;

int n,m,f[MAXN];
int v[MAXN],w[MAXN];

int main(void)
{
    cin>>n>>m;
    
    for(int i=1;i<=n;i++)
        cin>>v[i]>>w[i];
        
    for(int i=1;i<=n;i++)
        for(int j=m;j>=0;j--){//为什么要逆序求f:因为要做到代码的等价替换,二维形式下是f[i][j]是从f[i-1][j]和f[i-1][j-vi]+wi中找最大值
        					//一维数组中只能保存上一轮(i-1)中的状态值;更新本轮(i)状态值需要用到上一轮中体积较小的状态值,所以只能逆序处理;
        					//如果正序,会导致上一轮体积较小的状态值被更新,方程意义就和二维形式下的不一样了,变成错误的、意义不明的状态值
            if(j>=v[i])//采用一维写法,是二维写法代码的等价替换,其实际意义仍参考二维代码
                f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
        
    cout<<f[m]<<endl;
    
    return 0;
}

题目:Acwing.3 完全背包问题题目链接

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int MAXN=1010;

int n,m,f[MAXN][MAXN];
int v[MAXN],w[MAXN];

int main(void)
{
    cin>>n>>m;
    
    for(int i=1;i<=n;i++)
        cin>>v[i]>>w[i];
        
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++){//完全背包也可以转为一维形式,而且这里是正序,参考其二维形式也就不难理解为什么是正序了
            f[i][j]=f[i-1][j];
            if(j>=v[i])
                f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
        }
        
    cout<<f[n][m]<<endl;
    
    return 0;
}
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;
const int MAXN=1010;

int v[MAXN],w[MAXN],n,m,f[MAXN];

int main(void)
{
    cin>>n>>m;
    
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    
    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++)
            if(j>=v[i])//这是完全背包的一维优化形式,不难看出,问题的关键在于删去一维后,第i次循环中计算需要的到底是第i次的值(例如这里)还是第i-1次的值(例如01背包)若是前者,则j不需要逆序,若为后者,j需要逆序。
                f[j]=max(f[j],f[j-v[i]]+w[i]);
    }
    
    cout<<f[m]<<endl;
    
    return 0;
}

题目:Acwing.4 多重背包问题I题目链接

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int MAXN=110;

int n,m,f[MAXN][MAXN];
int v[MAXN],w[MAXN],cnt[MAXN];

int main(void)
{
    cin>>n>>m;
    
    for(int i=1;i<=n;i++)
        cin>>v[i]>>w[i]>>cnt[i];
        
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++){
            f[i][j]=f[i-1][j];
            for(int k=1;k<=cnt[i];k++)
                if(j>=k*v[i])
                    f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
        }
        
    cout<<f[n][m]<<endl;
    
    return 0;
}

题目:Acwing.5 多重背包问题II题目链接

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int MAXN=13000,MAXM=2010;//二进制打包,利用倍增的思想,将多重背包问题转化为01背包问题

int n,m,f[MAXN];
int v[MAXN],w[MAXN];

int main(void)
{
    cin>>n>>m;
    
    int cnt=0;
    for(int i=1;i<=n;i++){
        int a,b,c;
        cin>>a>>b>>c;
        
        int k=1;
        while(k<=c){//k=1,2,4,8,...,2^n (c<2^(n+1))
            cnt++;
            v[cnt]=a*k;
            w[cnt]=b*k;
            c-=k;
            k*=2;
        }
        if(c>0){//不够2的整数次幂的剩余物品打成最后一个包
            cnt++;
            v[cnt]=a*c;
            w[cnt]=b*c;
        }
    }
    
    n=cnt;
    
    for(int i=1;i<=n;i++)
        for(int j=m;j>=0;j--)
            if(j>=v[i])
                f[j]=max(f[j],f[j-v[i]]+w[i]);
        
        
    cout<<f[m]<<endl;
    
    return 0;
}

题目:Acwing.9 分组背包问题题目链接

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int MAXN=110;

int n,m,f[MAXN][MAXN],v[MAXN][MAXN],w[MAXN][MAXN],cnt[MAXN];

int main(void)
{
    cin>>n>>m;
    
    for(int i=1;i<=n;i++){
        cin>>cnt[i];
        
        for(int j=1;j<=cnt[i];j++)
            cin>>v[i][j]>>w[i][j];
        
    }
    
    for(int i=1;i<=n;i++)
        for(int j=m;j>=0;j--){
            f[i][j]=f[i-1][j];
            for(int k=1;k<=cnt[i];k++)
                if(j>=v[i][k])
                    f[i][j]=max(f[i][j],f[i-1][j-v[i][k]]+w[i][k]);
        }
        
    cout<<f[n][m];
    
    return 0;
}

题目:Acwing.7 混合背包问题题目链接

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;
const int MAXN=1010;

int n,m,f[MAXN];

struct Obj{
    int k,v,w;
};
vector<Obj> things;

int main(void)
{
    cin>>n>>m;
    
    for(int i=0;i<n;i++){
        int a,b,c;
        cin>>a>>b>>c;
        
        if(c==-1)
            things.push_back({-1,a,b});
        else if(c==0)
            things.push_back({0,a,b});
        else{
            for(int k=1;k<=c;k<<=1){//将多重背包问题通过倍增打包转化为01背包问题
                c-=k;
                things.push_back({-1,a*k,b*k});
            }
            if(c>0)
                things.push_back({-1,a*c,b*c});
        }
    }
    
    for(auto it:things){
        if(it.k<0){//01背包
            for(int j=m;j>=it.v;j--)
                f[j]=max(f[j],f[j-it.v]+it.w);
        }else{//完全背包
            for(int j=it.v;j<=m;j++)
                f[j]=max(f[j],f[j-it.v]+it.w);
        }
    }
    
    cout<<f[m]<<endl;
    
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值