背包问题汇总

首先最知名的当属dd大牛的背包九讲(点击下载)
DD engi 背包九讲的个人整理
背包不一定要装满,或许会有一定空余,但是此时价值是最大的

在这里插入图片描述

一、01背包

特点:对于每个物品只有选和不选两种状态,每件物品最多用一次

d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) dp[i][j]=max(dp[i1][j],dp[i1][jw[i]]+v[i])

例题

在这里插入图片描述

样例
Input
4 5
1 2
2 4
3 4
4 5
Output
8
代码

所有状态 d p [ 0 − > n ] [ 0 − > c ] dp[0->n][0->c] dp[0>n][0>c],如果从0件物品中选,所有价值都是0

初始情况: d p [ 0 ] [ 0 − > c ] = 0 dp[0][0->c]=0 dp[0][0>c]=0
在这里插入图片描述

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e4+5;
int v[maxn],w[maxn];//二维动态规划
int dp[maxn][maxn];//表示前i个物品,当前使用重量为j的最大价值
int main()
{
    int n,c;
    cin>>n>>c;
    for(int i=1;i<=n;i++)
    cin>>w[i]>>v[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=c;j++)//c是容量
        {
            dp[i][j]=dp[i-1][j];//一定存在,而右边状态不一定存在
            if(w[i]<=j)//要判断能不能装下
            dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
        }
    }
   // cout<<dp[n][c]<<endl; 可以重量不达到c,也可以不装满所有n个物品,注意题目中的“前”
   int ans=0;
   for(int i=0;i<=c;i++)
   ans=max(ans,dp[n][i]);//找出最大价值
   cout<<ans<<endl;
    return 0;
}
一维优化

d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]) dp[i][j]=max(dp[i1][j],dp[i1][jw[i]]+v[i])

在求解dp[i][j]时,只用到了dp[i-1][k]这一层,所以可以用滚动数组来做。另外对于j来说,j≤j,j-w[i]≤j,都分布在j的一侧,所以可以该成用一维数组来算。

从大到小去枚举j

d p [ j ] = m a x ( d p [ j ] , d p [ j − w [ i ] ] + v [ i ] ) dp[j]=max(dp[j],dp[j-w[i]]+v[i]) dp[j]=max(dp[j],dp[jw[i]]+v[i])

核心优化过程
 	for(int i=1;i<=n;i++)//遍历所有物品
        //for(int j=w[i];j<=c;j++)
        for(int j=c;j>=w[i];j--)
        {
            //dp[j]=dp[j];//恒等式 直接去掉
            //if(w[i]<=j)//改成直接从w[i]开始
            //dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//如果直接删掉第一维,会出现一些问题
           //<=>dp[i][j]=max(dp[i][j],dp[i][j-w[i]]+v[i]);
            //j-w[i]<j,所以在第i层会是dp[j-w[i]]+v[i]先被算了,实际会是dp[i][j-w[i]]
            //这样不对,应该要是dp[i-1],所以我们应该从大到小枚举
            //这样使得上式在用dp[j-w[i]]的时候还是i-1层的
        }
完整代码
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e3+5;
int v[maxn],w[maxn];//二维动态规划
int dp[maxn];//当前使用重量为j的最大价值
//注意:初始化很关键
//这里所有的位置都被初始化为0,所以dp[i]的含义变成了重量小于等于i时的最大价值
//k<m时
//dp[k]=max_v;
//dp[0]=0  -->  f[w[0]]=v[0]  -->...
//dp[m-k]=0  -->  f[m-k+w[0]]=v[0]  -->...  相比较而言 不过是多了一个偏移量m-k
//所以不用去枚举所有质量啦,结果就是dp[c]
//而如果 最开始初始化成 dp[0]=0,dp[其他]=-inf
//则所有状态都将由dp[0][0]转移而来,最后就要for循环去找最大的dp[i]

//若题意变成了 重量恰好是c时的最大价值
//则初始化dp[0]=0,dp[i]=-inf,  最终结果就是dp[c]
int main()
{
    int n,c;
    cin>>n>>c;
    for(int i=1;i<=n;i++)
    cin>>w[i]>>v[i];

    for(int i=1;i<=n;i++)
        for(int j=c;j>=w[i];j--)//换一下 从大到小枚举,w[i]>0 则j-w[i]一定是没有被算过的,保证j-w[i]是i-1的 而不是i的
        {
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }

   // cout<<dp[n][c]<<endl; 可以重量不达到c,也可以不装满所有n个物品,注意题目中的“前”
   //int ans=0;
   //for(int i=0;i<=c;i++)
   //ans=max(ans,dp[i]);//找出最大价值
   cout<<dp[c]<<endl;
    return 0;
}
二、完全背包

特点:这些物品都可以被选择无穷多次 <=>该物品有无穷多个

从小到大去枚举j
在这里插入图片描述

题面

在这里插入图片描述

样例
Input
4 5
1 2
2 4
3 4
4 5
Output
10
朴素做法

直接写时间复杂度会很高,最坏情况 O ( n ∗ w 2 ) O(n*w^2) O(nw2) -> 109 370ms

for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
            for(int k=0;k*w[i]<=j;k++)
                dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
                
cout<<dp[n][m]<<endl;

在这里插入图片描述
64ms

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

需要注意的是,这里从小到大枚举,是为了让dp[j]=max(dp[j],dp[j-w[i]]+v[i]);中的dp[j-w[i]]+v[i]i-1层的。

从小到大去枚举j,保持 dp[j-w[i]] 是第 i 层的
d p [ j ] = m a x ( d p [ j ] , d p [ j − w [ i ] ] + v [ i ] ) dp[j]=max(dp[j],dp[j-w[i]]+v[i]) dp[j]=max(dp[j],dp[jw[i]]+v[i])

40ms

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e3+5;
int v[maxn],w[maxn];//二维动态规划
int dp[maxn];//当前使用重量为i的最大价值
//仍然可以从二维优化到一维,所以我们直接用一维做就好了
//与01背包的区别在于,每件物品可以用无限次  这个可以用dfs?
//把所有值都初始化成了0
int main()
{
    int n,c;
    cin>>n>>c;
    for(int i=1;i<=n;i++)
    cin>>w[i]>>v[i];
    
    for(int i=1;i<=n;i++)
    {
        for(int j=w[i];j<=c;j++)//换回来,就可以表示可以取无限多次,就变成了完全背包
        {
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
            //从小到大的话,dp[j-w[i]]其实就已经被算过了, 其中可能已经包括了若干个第i个物品了
            //(所有比j小的都被算过了,注意这里的dp[j]=...dp[j-w[i]])
            //所以一定可以枚举到最优解
        }
/*通过数学归纳法证明
1.假设考虑前i-1个物品之后,所有的dp[j]都是正确的
2.现在要证明,考虑前i个物品后,所有的dp[j]也都是正确的
        
 对于某个j而言,如果最优解中包含k个v[i]
从小到大枚举时,一定可以枚举到dp[j-k*w[i]]  我们会用dp[j-k*w[i]-w[i]]+v[i]来更新它 
 //         -->  dp[j-(k-1)*w[i]-w[i]]+v[i]=dp[j+"w[i]"-k*w[i]]+v[i]    包含一个i物品
//          -->  dp[j-(k-2)*w[i]-w[i]]+v[i]                             包含两个i物品
//                         ......
//          -->  dp[j-w[i]]+v[i] ==dp[j-(k-(k-1))*w[i]]     计算过只包含k-1个i物品这种状态
//          -->  dp[j]                                    就包含了k个i物品  -->找到最优解
*/
        
   /* 
    for(int j=c;j>=w[i],j--)
        for(int k=0;k*w[i]<=j;k++)
            dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);//可以选无穷多个  
            //dp[j-k*w[i]] 这个里面是一定没有选第i个物品的 是i-1的
            //自己列一下01背包的动态规划二维表也许更好理解?
    */
    } 
   // cout<<dp[n][c]<<endl; 可以重量不达到c,也可以不装满所有n个物品,注意题目中的“前”
   //int ans=0;
   //for(int i=0;i<=c;i++)
   //ans=max(ans,dp[i]);//找出最大价值
   cout<<dp[c]<<endl;
    return 0;
}
三、多重背包问题

特点:每种物品有个数限制,是01背包的扩展
在这里插入图片描述状态划分:枚举每种物品选几个

完全背包,受背包容量限制,最多到dp[i-1][j-k*w[i]]+(k-1)*v[i], 所以dp[i][j-w[i]] = max(…)最多有k项。

而上述多重背包,是受物品个数限制,最多可以到dp[i-1][j-(s+1)*w[i]]+s*v[i]导致会多出一项, 所以dp[i][j-w[i]]=max(…)最多有s+1项

完全背包是求前缀的最值,而多重背包是求滑动窗口的最值

题面

在这里插入图片描述

样例
Input
4 5
1 2 3
2 4 1
3 4 3
4 5 2
Output
10
0≤n,c≤100 0≤w,v,s≤100

通过输入规模可以判断复杂度 可用三次方

暴力解法: O(ncs)
二维
   for(int i=1;i<=n;i++)
        for(int j=0;j<=m;j++)
            for(int k=0;k<=s[i]&&k*w[i]<=j;k++)
                dp[i][j]=max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
                //max对每一轮的dp[i][j]取最大值
/*  01背包的扩展
做状态转移的时候多加一重循环即可

dp[j]=max(dp[j],dp[j-w[i]]+v[i] 选一个,dp[j-2*w[i]]+2*v[i]  选两个 ...);

关于初始化,还是和之前一样
1.dp[i]=0
    ans=dp[c]
    
2.dp[0]=0,dp[i]=-inf
    ans=max{dp[0....c]}
    
*/
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn=1e3+5;
int v[maxn],w[maxn],s[maxn];//二维动态规划
int dp[maxn];//当前使用重量为i的最大价值
int main()
{
    int n,c;
    cin>>n>>c;
    for(int i=1;i<=n;i++)
    cin>>w[i]>>v[i]>>s[i];
    
    for(int i=1;i<=n;i++)
    {
        for(int j=c;j>=w[i];j--)
        {
            for(int k=1;k<=s[i]&&k*w[i]<=j;k++)//注意这里k从1开始噢,如果是0就没啥意思了诶,直接dp[j]=dp[j] 注意它后面与的条件,进行一个优化
            dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);//虽然从0开始也能ac 唔...
        }
    }
    cout<<dp[c]<<endl;
    return 0;
}
多重背包的二进制优化
数据范围扩大 0≤n≤1000,0≤c≤2000,w,v,s≤2000

暴力做的话,计算次数可能为1000*2000*2000=4*10^9,会超时。
在这里插入图片描述

二进制优化:复杂度O(nclogs)

分组存储

#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=2e4+5;//1000*log2000 ~ 12000
const int mod=1e9+7;
int dp[2010];
int w[maxn],v[maxn];
int main()
{
    int n,c;
    cin>>n>>c;
    int k=0;
    for(int i=1;i<=n;i++)
    {
        int ww,vv,s;
        cin>>ww>>vv>>s;
        int t=1;//从1开始依次打包成1,2,4,8...
        while(t<=s)
        {
            w[k]=ww*t;
            v[k]=vv*t;
            s-=t;
            t<<=1;
            k++;
        }
        if(s>0)
        {
            w[k]=ww*s;
            v[k]=vv*s;
            k++;
        }
    }
    for(int i=0;i<k;i++)
        for(int j=c;j>=w[i];j--)
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
    
    cout<<dp[c]<<endl;
    return 0;
}

结构体实现

/*
    要把一个多重背包变成01背包,怎么做?  把s个物品拆开嘛,那不就是单个单个的了?-->01背包
    最多会有1000*2000=2*10^6个物品,复杂度很高噢2*10^6 * 2000 =4*10^9  也会超时啦
    所以我们要用二进制拆法
    eg:7   最少选多少个数,能够把<7的所有数表示出来,每个数可以选也可以不选
           1 1 1 1 1 1 1   (7个1)
           至少要三个数  因为:每个数两种选法  ,log2 (8)=3  所以下界一定是3
           1 2 4  (2^0  2^1  2^2)
    
    给定任意一个数s,问最少需要多少个数能够把<s的所有数表示出来 每个数有选和不选两种状态
    log2 (s) 上取整
    
    but s=10怎么办呢  不能1 2 4 8 会超过10的  (11 12 13 14 15都是不能选的,因为s是物品的上限个数)
     1 2 4 3(s-1-2-4)    -->每个数分成的是log2 (s)份
     
     --> c=1000*log(2000)个物品   复杂度 =1000*11 *2000 =2*10^7 刚刚好
     
*/
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <vector>
using namespace std;
const int maxn=2e3+5;
//int v[maxn],w[maxn],s[maxn];//二维动态规划
int dp[maxn];//当前使用重量为i的最大价值
struct node{
    int w,v;
};
int main()
{
    vector<node> goods;
    int n,c;
    cin>>n>>c;
    for(int i=1;i<=n;i++)
    {   
        int w,v,s;
        cin>>w>>v>>s;
        for(int k=1;k<=s;k*=2)
        {
            s-=k;//开始拆分咯
            goods.push_back({w*k,v*k});//k份k份看成一个整体 注意这样一整个整体放进去的写法
        }
        if(s>0)
        goods.push_back({w*s,v*s});
    }
    
    for(auto g:goods)//auto自动类型  遍历goods
        {
            for(int j=c;j>=g.w;j--)
            {
                dp[j]=max(dp[j],dp[j-g.w]+g.v);
            }
        }
        
    cout<<dp[c]<<endl;
    return 0;
}
多重背包的单调队列优化
题面
数据范围再扩大 0≤n≤1000,0≤c≤20000 w,v,s≤20000

硬算 O ( n c s ) = 1 0 3 ∗ 4 ∗ 1 0 8 = 4 ∗ 1 0 1 1 O(ncs)=10^3*4*10^8=4*10^11 O(ncs)=1034108=41011 必炸

二进制优化的复杂度: 1000 ∗ l o g 2 ( 20000 ) ∗ 20000 = 1000 ∗ 15 ∗ 20000 = 3 ∗ 1 0 8 1000*log_2 (20000)*20000=1000*15*20000=3*10^8 1000log2(20000)20000=10001520000=3108 会炸

楼天城的《男人八题》什么时候去写一下? 噢还有网络流14题

代码

因为要求的是滑动窗口最大值,所以可以用单调队列来优化。

for(int i=1;i<=n;i++)
    {
        for(int j=c;j>=w[i];j--)
        {
            for(int k=1;k<=s[i]&&k*w[i]<=j;k++)//观察它这个决策部分,看能不能用什么数据结构去优化掉
            dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);
        }
    }

/*
把枚举的质量归个类 以%w不同余进行归类,一共会有w类,两两之间相互独立
*/
四、分组背包问题

特点:物品有n组,每组中有若干个物品,每组只能选一个
在这里插入图片描述

例题

在这里插入图片描述

输入
3 5
2
1 2
2 4
1
3 4
1
4 5
输出
8
代码
#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=105;
const int mod=1e9+7;
int dp[2010];
int w[maxn][maxn],v[maxn][maxn],s[maxn];
int main()
{
    int n,c;
    cin>>n>>c;
    for(int i=1;i<=n;i++)
    {
        cin>>s[i];
        for(int j=0;j<s[i];j++)
        cin>>w[i][j]>>v[i][j];
    }
    for(int i=1;i<=n;i++)
        for(int j=c;j>=0;j--)
            for(int k=0;k<s[i];k++)
                if(w[i][k]<=j)
                dp[j]=max(dp[j],dp[j-w[i][k]]+v[i][k]);
    
    cout<<dp[c]<<endl;           
    return 0;
}
五、二维费用的背包问题

六、混合背包问题

七、有依赖的背包问题

本篇致谢yxc老师的背包九讲专题,获益匪浅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值