背包问题

背包问题大锅饭

​ 每次看见背包问题之后都会忘记怎么做接下来我们来看看怎样解决一些经典的背包问题以及其优化的思路

首先背包分为以下几种:

1.01背包问题: 给你一个背包体积容量,每一个物品只能选择和不选(每个物品有体积和价值,我们追求最后选择 完成之后的价值最大情况)

2.完全背包问题:同上,只是每一个物品可以选择无限次数

3.多重背包问题: 给你一个背包体积容量,每一个物品有有限的选择次数

4.分组背包问题:物品被分为有限的组数,你每次最多从一组中选择一个物品

那么首先来看01背包问题:
状态表示:f(i,j),表示我们在当前面对前i个物品的时候,我们用了j的空间,所得到的最大价值
状态分析:我们面对第i个物品的时候,其实就是可以选和不选,那么我们就是选这两种情况的最大值就可以了
状态方程:f(i,j)=max(  f(i-1,j),f(i-1,j-v)+w)  //v表示第i个物品的体积,w表示价值
                      不选         选

接下来直接整代码:

#include <iostream>
#include <algorithm>
using namespace std;
//这儿解释一下我们的输入输出  首先输入物品数、和背包容量
const int N=1010;
int v[N],w[N];
int f[N][N];
int main(){
    int n,m;
    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-1][j-v[i]]+w[i]);
        }
    cout<<f[n][m];
    return 0;
}

那么我们就要开始优化,首先我们只会用到每次更新迭代只会用到i-1行,所以我们可以在一个一维数组更新状态就可以了:

int f[N];
------------------
    for(int i=1;i<=n;i++)  //这一行不能删除,还是必须地更新那么多次
        for(int j=0;j<=m;j++){
            //f[i][j]=f[i-1][j];这一行直接删除,直接在用上一次的数据就是我们想要的i-1的数据
            //接下来修改下面一行
            if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);//j>v[i]才有用
            //那么我们可以直接在每次j就从v[i]开始不就行了?
        }
-------------------
   改:错误版本
      for(int i=1;i<=n;i++)  
        for(int j=v[i];j<=m;j++){
			f[j]=max([j],f[j-v[i]]+w[i]); //改成这样就可以了吗?
            //我们试着思考一下,当第i次更新中,j是从小到大,我们f[j-v[i]]肯定已经是被更新过了的,也就				
            //是说我们f[j-v[i]]肯定是i次更新的结果,如果换成二维的就是f[i][j-v[i]],所以不是我们想要			
            //的f[i-1][j-v[i]],解决的问题很简单,我们j从大到小就可以了,这时候的f[j-v[i]]是没有更新			
            //过后的。
        }
------------------
    改:
      for(int i=1;i<=n;i++)  
        for(int j=m;j>=v[i];j--){
			f[j]=max([j],f[j-v[i]]+w[i]); 
        }
其次我们再来看看完全背包问题:
状态表示:f(i,j),表示我们在当前面对前i个物品的时候,我们用了j的空间,所得到的最大价值
状态分析:我们面对第i个物品的时候,其实就是可以不选和选1个、2...k个,那么我们就是选这几种情况的最大值就可以了
状态方程:f(i,j)=max(  f(i-1,j),f(i-1,j-v)+w,f(i-1,j-2v)+2w,...f(i-1,j-kv)+kw) 
              这儿引入了k的概念,所以我们在循环的时候,就要加入k的概念        

代码:

const  int N=1010;
int n,m;
int f[N][N];
int v[N],w[N];
int main (){
    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++){
            for(int k=0;k*v[i]<=j;k++)
                f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
        }
    cout<<f[n][m];
    return 0;
    
}
//上面的代码三重循环,你受得了吗?
因此我们分析一下递推公式: 常用的手法就是让j或则i减去一个‘等级’
f(i,j)  =max(f(i-1,j),f(i-1,j-v)+w,f(i-1,j-2v)+2w,...f(i-1,j-kv)+kw)
f(i,j-v)=max(		  f(i-1,j-v),  f(i-1,j-2v)+w,... f(i-1,j-kv)+kw+w)
对比以上的公式我们会发现一个公式:
f(i,j)=max(f(i-1,j),f(i,j-v)+w);
这个公式是不是很熟悉?我们把01背包的拿过来
f(i,j)=max(f(i-1,j),f(i-1,j-v)+w)//01  接下来的代码会改了吗?但是注意一个是i-1,一个是i
//所以这儿我们j应该是从前往后而不是从后往前;
 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]);
        }
//over-----------
多重背包问题

多重背包问题其实和完全背包一样,只是完全背包的k由j限制,多重背包的k即被j限制也被每种物品的个数限制。

int n,m;
int f[N][N];
int v[N],w[N],s[N];
int main (){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
    for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++){
            for(int k=0;k*v[i]<=j&&k<=s[i];k++)  //就是这个不一样
                f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
        }
    cout<<f[n][m];
    return 0;
}

做到这里很自然的想到是否能够像之前的完全背包一样利用递推公式优化,但是你去试一下,不行的;

所以这儿要优化可以采用二进制优化方法

思想:首先我们假想一个物品,那么假设可以选S次,那么我们可不可以把其拆成多个物品,那么刚好这些物品的组合类型就是可以完全成就我们对于这一个物品选择的所有情况;

比如我们对于一个物品有着12次的选择机会,其二进制的代码为1100;
那么我们就可以将拆其成1+2+4+5的组合;这四个数完全组合成0-12的所有情况,具体证明自己去完成
那么根据以上的思想,我们都可以对于每一个物品都进行这种拆分,那么后面我们所有的问题不就都是01背包问题了吗?
------代码
int n, m;
int v[N], w[N];
int f[M];
int main()
{
    cin >> n >> m;
    int cnt = 0;
    --------以下为开始读入以及拆分
    for (int i = 1; i <= n; i ++ )
    {
        int a, b, s;
        cin >> a >> b >> s;
        int k = 1;
        while (k <= s)
        {
            cnt ++ ;
            v[cnt] = a * k;
            w[cnt] = b * k;
            s -= k;
            k *= 2;
        }
        if (s > 0)
        {
            cnt ++ ;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }
----------------后面的就和01背包的问题一样了
    n = cnt;
    for (int i = 1; i <= n; i ++ )
        for (int j = m; j >= v[i]; j -- )
            f[j] = max(f[j], f[j - v[i]] + w[i]);

    cout << f[m] << endl;

-------------其实多重背包还有一个单调队列的方法以后再详细的补充

分组背包问题
状态表示:f(i,j),表示我们在当前面对前i个物品的时候,我们用了j的空间,所得到的最大价值
状态分析:我们面对第i个分组的时候,其实就是不选和或则选择其中的一个,那么我们就是选这几种情况的最大值就可以了。
状态方程:f(i,j)=max(  f(i-1,j),f(i-1,j-v1)+w1,f(i-1,j-v2)+w2....) 
              所以在j循环下,我们概要依次去遍历每个组的情况;
//------------这个比较简单,我就直接上代码了-------------//
int n, m;
int v[N][N], w[N][N], s[N];  
int f[N];
int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ )
    {
        cin >> s[i];
        for (int j = 0; j < s[i]; j ++ ) 
            cin >> v[i][j] >> w[i][j]; //第i组的物品们的 体积和价值
    }
    for (int i = 1; i <= n; i ++ )
        for (int j = m; j >= 0; j -- )//注意还是i-1,所以还是从后往前
            for (int k = 0; k < s[i]; k ++ )  //遍历每一个组的物品
                if (v[i][k] <= j)
                    f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);

    cout << f[m] << endl;

后面有时间再补充背包的其它问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值