背包问题详细整理

1.01背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。第 i 件物品的体积是 vi,价值是 wi。然后装包,求总价值最大。
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值

输入格式
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8

首先每件物品仅有一件
不选择该物品: f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j]=f[i-1][j] f[i][j]=f[i1][j]
选 择 该 物品: f [ i ] [ j ] = f [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) f[i][j]=f[i−1][j−w[i]]+v[i]) f[i][j]=f[i1][jw[i]]+v[i])
所以状态转移方程 f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) f[i][j]=max(f[i−1][j],f[i−1][j−w[i]]+v[i]) f[i][j]=max(f[i1][j],f[i1][jw[i]]+v[i])

优化:
其中i用来表示状态,但是可以用j直接表示状态,但是如果从前遍历,那么前面的数据每次都会被从新覆盖,所以j需要从后遍历

就可以写成 f [ j ] = m a x ( f [ j ] , f [ j − v ] + w ) f[j]=max(f[j],f[j-v]+w) f[j]=max(f[j],f[jv]+w);

完整代码

#include <bits/stdc++.h>
using namespace std;
int n,m;
int f[1010];
int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++){
        int v,w;
        cin>>v>>w;
        for(int j=m;j>=v;j--)
            f[j]=max(f[j],f[j-v]+w);
    }
    cout<<f[m]<<endl;
}

2.完全背包

01背包的延续,每件商品可以无限取。

那么每件商品就不是取不取两种状态,就得算作取0件,1件,2件。。。这么去下去
先从二维数组分析,那就是再加一个循环 循环 0 < = k ∗ w [ i ] < = j 0<=k∗w[i]<=j 0<=kw[i]<=j
f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j − k ∗ w [ i ] ] + k ∗ v [ i ] ) ∣ f[i][j]=max(f[i−1][j−k∗w[i]]+k∗v[i])∣ f[i][j]=max(f[i1][jkw[i]]+kv[i])

优化
其中i依然可以去掉,用j来表示,这时候记得01背包说过,前面的数据会被覆盖掉,但是,完全背包,因为每件商品无限取,所以不用考虑覆盖,只考虑怎么拿最多就可以,看了好几个教程都感觉有点懵逼,于是debug一下,豁然开朗,
我还贴心的加了理解提示 (╹▽╹)
在这里插入图片描述
好了 贴代码

#include <bits/stdc++.h>
using namespace std;
int n,m;
int f[1010];
int main(){
    cin>>n>>m;
    for (int i=0;i<n;i++){
        int v,w;
        cin>>v>>w;
        for(int j=v;j<=m;j++)
            f[j]=max(f[j],f[j-v]+w);
    }
    cout<<f[m]<<endl;
}

3.多重背包1

多重背包限制了每件商品的个数,最多有多少个,相比完全背包,更应该把他比作01背包的延续

那么每件商品取法
取0件,1件,2件。但是最多取s件,
所以 可以比成这些多出来的商品也是独立的商品,在01背包基础上多一个循环。

直接贴代码了

#include <bits/stdc++.h>
using namespace std;
int n,m;
int f[1010];
int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++){
        int v,w,s;
        cin>>v>>w>>s;
        for(int j=m;j>=v;j--)
            for(int k=1;k<=s && k*v<=j;k++)
                f[j]=max(f[j],f[j-k*v]+k*w);
    }
    cout<<f[m]<<endl;
}

4.多重背包2 (二进制优化)

多重背包在数据规模 v,w,s皆为100时候还可以
可是如果看到数据规模100020002000的时候就要考虑二进制优化了

首先思考一个问题,
给定一个任意的数,比如7,需要多少个数可以表示出0-7中任意一个数字
把七转换成二进制 就可以发现只需要 1 2 4这三个数即可表示7以内任意数
比如6用2+4。。。。
也就是需要log2x向上取整个。
如果遇到13,其实我们不需要1 2 4 8 这么大的数,直接用13-7=6 其中7表示目前的数可以标识出的最大值
那么就用1 2 4 6即可表示出所有的数

那么回头看这个问题,如果需要判断不超过某数量的商品,就不需要全部遍历了
比如1023件商品,那么就可以通过10次遍历即可求出
首先插入代码块,通过代码来理解这个过程(看注释)

#include <bits/stdc++.h>
using namespace std;
int n,m;
int f[2010];
struct Good{int v,w;};
int main(){
    vector<Good> goods;//定义一个模板,包含v,w;
    cin>>n>>m;
    for(int i=0;i<n;i++){
        int v,w,s;
        cin>>v>>w>>s;
        for(int k=1;k<=s;k<<=1){
            s-=k;//s把每次k(1,2,4,8,16)减去,表示还能拿多少件
            goods.push_back({v*k,w*k});
            //存入的就是分别取1,2,4,8,16件商品的价值和重量
        }
        if(s>0)goods.push_back({v*s,w*s});//把最后剩下的存放进去,就可以表示任意值
    }
    for(auto good:goods)
        for(int j=m;j>=good.v;j--)
            f[j]=max(f[j],f[j-good.v]+good.w);
    cout<<f[m]<<endl;
}

同时Debug一下看下运行流程

在这里插入图片描述

3.多重背包 (分组优化)

看了半天没看懂,代码先敲了一遍。记录下来,有时间再研究。

#include <bits/stdc++.h>
using namespace std;
int n,m;
const int N=20010;
int f[N],g[N],q[N];
int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++){
        int v,w,s;
        cin>>v>>w>>s;
        memcpy(g,f,sizeof f);
        for(int j=0;j<v;j++){
            int hh=0,tt=-1;
            for(int k=j;k<=m;k+=v){
                f[k]=g[k];
                if(hh <= tt && k-s*v >q[hh])hh++;
                if(hh <= tt) f[k]=max(f[k],g[q[hh]]+(k-q[hh])/v *w);
                while(hh <= tt && g[q[tt]]-(q[tt]-j)/v*w<=g[k]-(k-j)/v*w)tt--;
                q[++tt]=k;
            }
        }
    }
    cout<<f[m]<<endl;
}

4.混合背包问题

看完了前面那个多重背包优化,再来看这个混合背包,真是有一种舒畅的感觉。
混合背包就是物品选择有01背包,完全背包,多重背包多种方式,并且数据规模也没有前一个那么大
那么就可以使用二进制优化
然后把前面的代码拼凑起来就OK了

#include <bits/stdc++.h>
using namespace std;
int n,m;
int f[2010];
struct Good{int kind,v,w;};
int main(){
    cin>>n>>m;
    vector <Good>goods;
    for(int i=0;i<n;i++){
        int v,w,s;
        cin>>v>>w>>s;
        if(s<0)goods.push_back({-1,v,w});
        else if(s==0)goods.push_back({0,v,w});
        else{
            for(int k=1;k<s;k<<=1){
                s-=k;
                goods.push_back({-1,v*k,w*k});
            }
            if(s>0)goods.push_back({-1,v*s,w*s});
        }
    }
    for(auto good:goods){
        if(good.kind<0)
            for(int j=m;j>=good.v;j--)f[j]=max(f[j],f[j-good.v]+good.w);
        else
            for(int j=good.v;j<=m;j++)f[j]=max(f[j],f[j-good.v]+good.w);
    }
    cout<<f[m]<<endl;
}

二维费用的背包问题

二维费用问题,其实和01背包非常的像,只是多了一个体积的限制,那么,01背包当时我们使用的是一维空间,那么,这里使用二维空间即可,直接看代码

#include <bits/stdc++.h>
using namespace std;
int n,v,m;
int f[110][110];
int main(){
    cin>>n>>v>>m;
    for(int i=0;i<n;i++){
        int a,b,c;
        cin>>a>>b>>c;
        for(int j=v;j>=a;j--)
            for(int k=m;k>=b;k--)
            f[j][k]=max(f[j][k],f[j-a][k-b]+c);
    }
    cout<<f[v][m]<<endl;
}

分组背包问题

分组背包问题就是给物品分组
每组只能使用一个商品
那么,每组商品就要分别取出最优的解
然后后面再去最优的解,会把前面不优的解覆盖

#include <bits/stdc++.h>
using namespace std;
int n,m;
int f[110],v[110],w[110];
int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++){
        int s;
        cin>>s;
        for(int j=0;j<s;j++)cin>>v[j]>>w[j];
        for(int j=m;j>=0;j--)
            for(int k=0;k<s;k++)
                if(j>=v[k])
                    f[j]=max(f[j],f[j-v[k]]+w[k]);
    }
    cout<<f[m]<<endl;
}

背包问题求方案数

求方案数需要多一个数组来统计方案。
代码比较容易理解

#include <bits/stdc++.h>
using namespace std;
int n,m;
int f[2010],g[2010];
const int mod=1e9+7;
int main(){
    cin>>n>>m;
    for(int i=0;i<=m;i++)g[i]=1;
    for(int i=0;i<n;i++){
        int v,w;
        cin>>v>>w;
        for(int j=m;j>=v;j--){
            int value=f[j-v]+w;
            if(value>f[j]){
                f[j]=value;
                g[j]=g[j-v];
            }else if(f[j]==value)
                g[j]=(g[j]+g[j-v])%mod;
        }
    }
    cout<<g[m]<<endl;
}

未完待续。。。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值