五种背包问题

01背包问题   每件物品只能用一次
完全背包        无限次用物品
多重背包问题  每件物品有有限件
分组背包问题  有有限组,每组中有若干个物品,每组只能选一个
混合背包   多种加和

dp分析法

        状态表示f(i,j)

                集合,状态表示的是哪一个集合

                                 所有选法

                                 条件:只从前i个物品那里选,总体积小于等于j

                属性,该集合的属性如:max min 数量等

        状态计算

                        集合的划分:不重不漏,不漏一定要满足,不重看情况

01背包

#include <iostream>
#include <algorithm>

using namespace std;
const int N = 1010;

int w[N],v[N];
int vol,n;
int dp[N][N];

int main()
{
    cin>>n>>vol;
    for(int i = 1;i<=n;i++){
        cin>>v[i]>>w[i];
    }
    for(int i = 1;i<=n;i++){
        for(int j = 1;j<=vol;j++){
           if(j<v[i]) dp[i][j] = dp[i-1][j];//判断是否能选这件物品
           else dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
           //状态转移,该状态可以由两个状态算出,一个是不选该物品,另一个是,选了该物品。
           //                                     dp[i-1][j]        dp[i-1][j-v[i]]+w[i];
        }
    }
    cout<<dp[n][vol];
    return 0;
}
//滚动数组优化版
#include <iostream>
#include <algorithm>

using namespace std;
const int N = 1010;

int w[N],v[N];
int vol,n;
int dp[N];

int main()
{
    cin>>n>>vol;
    for(int i = 1;i<=n;i++){
        cin>>v[i]>>w[i];
    }
    for(int i = 1;i<=n;i++){
        for(int j = vol;j>=v[i];j--){
            dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    cout<<dp[vol];
    return 0;
}

为什么能优化?

因为状态转移只用到了两层,i和i-1层,所以其他层不需要用,所以这样就可以把空间复杂度优化成一维。一维数组的含义是当取第i件物品时,在j的背包容量下,所能装下的最大的物品价值,

为什么要从vol减到v[i]?

如果正序更新的话,会出现数据被污染的情况,不优化的版本中,第i层的数据一定是i-1层更新的来的,但是优化成一维,会出现要用上一层的数据的时候,已经被更新过了,比如, 如果f[7]要  用f[3]来优化,如果正向就会出现,f[3]在f[7]更新之前就已经更新了,是第i层的数据而不是i-1层的数据

完全背包

朴素做法 就是枚举各个状态 不过TLE

#include <iostream>

using namespace std;
const int N = 1010;

int w[N],v[N];
int dp[N][N];
int n,vol;

int main()
{
    cin>>n>>vol;
    for(int i = 1;i<=n;i++) cin>>v[i]>>w[i];
    
    for(int i = 1;i<=n;i++){
        for(int j = 0;j<=vol;j++){
            for(int k = 0;k*v[i]<=j;k++){
               dp[i][j] = max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
            }
        }
    }
    cout<<dp[n][vol];
    return 0;
}

优化版 三维优化为两维

f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w ,  f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)
f[i , j-v]= max(            f[i-1,j-v]   ,  f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....)
由上两式,可得出如下递推关系: 
                        f[i][j]=max(f[i,j-v]+w , f[i-1][j]) 

所以得到了第一次优化

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

然而,还可以继续优化为一维,考虑到01背包的优化

#include<iostream>
using namespace std;
const int N = 1010;
int dp[N];
int v[N],w[N];
int main()
{
    int n,vol;
    cin>>n>>vol;
    for(int i = 1 ; i <= n ;i ++)
    {
        cin>>v[i]>>w[i];
    }

    for(int i = 1 ; i<=n ;i++)
    for(int j = v[i] ; j<=vol ;j++)
    {
            dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
    }
    cout<<dp[vol]<<endl;
}

多重背包问题

纯暴力与完全背包类似

#include <iostream>
using namespace std;
const int N = 110;
int w[N],v[N],s[N];
int dp[N][N];
int main()
{
    int n,vol;
    cin>>n>>vol;
    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<=vol;j++)
            for(int k = 0;k*v[i]<=j&&k<=s[i];k++){
                dp[i][j] = max(dp[i][j],dp[i-1][j-k*v[i]]+w[i]*k);
            }
        
        cout<<dp[n][vol];
    return 0;
}

但是,数据范围太大的时候,包超时的牢底。

下面是优化

如果参考完全背包的优化方法那么会得到

f[i,j] = max(f[i-1,j], f[i-1,j-v]+w, f[i-1,j-2v]+2w,.., f[i-1,j-sv] + sw ) 
f[i,j-v] = max(        f[i-1,j-v],   f[i-1,j-2v]+w,.., f[i-1,j-sv]+(s-1)w, f[i-1,j-(s+1)v]+sw )

我们发现永远无法把f[i,][j-v]里面的最后一项去掉,所以不能用完全背包的优化方法去优化

只有一条路:二进制优化方法

原理:二进制把每个数分成1  2  4  8  ......等二进制数,那么按照此等分割方式,我么可以把任意的数字分成几个二进制数字的和,所以多重背包中的物品数量就可以按组分成若干份。就不用一个一个枚举了,O(N)变为O(logN)---------就是把n个物品打包在一起,当成一个单独的物品

#include<iostream>

using namespace std;
const int N = 30010;
//为什么要开到30010?
//因为二进制的原因log2000 * 1000 约等于30000
int dp[N];
int w[N],v[N];

int main()
{
    int n,vol;
    cin>>n>>vol;
    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++;
            w[cnt] = k*b;
            v[cnt] = k*a;
            s-=k;
            k*=2;
        }
        if(s>0){
            cnt++;
            w[cnt] = s*b;
            v[cnt] = s*a;
            s = 0;
        }
    }
    for(int i = 1;i<=cnt;i++){//简单01
        for(int j = vol;j>=v[i];j--){
            dp[j] = max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    cout<<dp[vol]<<endl;
    return 0;
}

然后我们惊奇的发现,就相当于01背包问题了,magic。

分组背包问题

与上面类似

#include <iostream>
using namespace std;
const int N = 110;
int w[N][N],v[N][N];
int s[N];
int dp[N];
int main()
{
    int n,vol;
    cin>>n>>vol;
    for(int i  =1;i<=n;i++){
        cin>>s[i];
        for(int j = 1;j<=s[i];j++){
            cin>>v[i][j]>>w[i][j];
        }
    }
    for(int i = 1;i<=n;i++){
        for(int j = vol;j>=1;j--){
            for(int k = 1;k<=s[i];k++){
                if(v[i][k] <= j) { //必须要满足,否则下面的下标减出来是负数
                    dp[j] = max(dp[j]/*不选*/, dp[j - v[i][k]] + w[i][k]/*选*/);
                }
            }
        }
    }
    cout<<dp[vol];
    return 0;
}

混合背包

都看成多重背包做二进制优化就行了,对于完全背包的的项,可以算出来最多有几个这一项就可以了-------背包最大容量/该物品的体积

#include <bits/stdc++.h>
using namespace std;
int a[10005],b[10005];
int t=0,n,m,f[10005],w,v,s;
int main()
{

    cin>>n>>m;
    while(n--){
        cin>>v>>w>>s;
        if(s==-1)s=1;
        if(s==0)s=m/v;
        else s=s;
        for(int i=1;;i*=2){
           if(s<i)break;
            s-=i;
            a[++t]=v*i;
            b[t]=w*i;
        }
        if(s>0)a[++t]=v*s,b[t]=w*s;
    }
    for(int i=1;i<=t;i++)
    for(int j=m;j>=a[i];j--)
    f[j]=max(f[j-a[i]]+b[i],f[j]);
    cout<<f[m]<<endl;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值