[AcWing],背包九讲---过程推导+代码实现(全面)

https://www.acwing.com/problem/

01背包问题

在这里插入图片描述

分析

对于每一个物品,在背包容量足够的情况下,我们有两种状态,一种是把该物品放在背包,一种是不把该物品放在背包。

我们建立一个二维的数组, d p [ i ] [ j ] dp[ i ][ j ] dp[i][j]表示前 i i i 个物品,在容量为 j j j 的时候的最大价值。
V [ i ] V[ i ] V[i] 表示物品所占的体积 , W [ i ] W[ i ] W[i] 表示物品的价值

  • 不把该物品放在背包: d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[ i ][ j ] = dp[ i - 1][ j ] dp[i][j]=dp[i1][j],我们直接顺延上一个物品选择的结果
  • 把该物品放在背包: d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − V [ i ] ] + W [ i ] ) dp[ i ][ j ]=max(dp[ i ][ j ],dp[i-1] [ j-V[ i ] ] + W[ i ]) dp[i][j]=max(dp[i][j]dp[i1][jV[i]]+W[i])
    因为要把这个物品放在背包,所以背包中首先得有该物品的容量大小。
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1010;

int dp[N][N];
int V[N],W[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++)
        {
   
            dp[i][j] = dp[i-1][j];//不选当前物品
            //选当前物品
            if(j >= V[i]) dp[i][j] = max(dp[i][j], dp[i-1][j-V[i]] + W[i]);
        }
    cout<<dp[n][m]<<endl;
    return 0;
}

在动态规划中,对于这种当前状态只与前一层的状态有关的情况,我们就可以对数组进行压缩,可以把二维变成一维。

在这里插入图片描述
改进后
在这里插入图片描述

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1010;

int dp[N];
int V[N],W[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 = m; j >= V[i]; j--)
            dp[j] = max(dp[j], dp[j - V[i]] + W[i]);
    
 
    cout<<dp[m]<<endl;
    return 0;
}

拓展问题

上面的问题求的是,背包中物品的总价值最大,但是此时背包确是不一定满的。

如果问题改为,求背包中物品的体积之和恰好为背包的容量的时候,怎么做呢?我们可以在初始化的时候做点手脚。
在这里插入图片描述
通过改进后,我们就可以确保 d p [ n ] dp[ n ] dp[n]的值是从初始状态转移过来的。也就是背包中物品体积恰好为背包体积时,背包中物品的最大价值,

完全背包

在这里插入图片描述

分析

完全背包问题与01背包的一个不同点就是,完全背包问题中一个物品在不超过背包体积的情况下,可以无限用。
与01背包相比,只有一个小小的变化,我们也可以引用01背包问题的思路

  • dp[ i ] :表示当前背包体积时,物品的最大价值
  • 状态转移方程:max(dp[ j ], dp[ j - V[ i ]] + W[ i ])

与01背包相比,改进的地方
在这里插入图片描述
因为需要考虑无限放的问题,就可以从前向后遍历。这样我们在每一个体积的时候,看上去值减去了一个物品的重量,但是我们是从前向后的,我们在后面的每一次状态,其实就可以看成是一个迭代的的过程。他是在前面已经选择该物品的前提下,再选一次这个物品。
在这里插入图片描述

#include <iostream>
using namespace std;

const int N = 1010;

int dp[N];
int n,m;

int main()
{
   
    cin>>n>>m;
    
    for(int i = 1; i <= n; i++)
    {
   
        int v,w;
        cin>>v>>w;//v 物品体积  w 物品重量
        
        for(int j = v; j <= m; j++)
            dp[j] = max(dp[j], dp[j - v] + w);
    }
    
    cout<<dp[m]<<endl;
    return 0;
}

多重背包 I

在这里插入图片描述

分析

与01背包不同的是,多重背包的每一个物品有 S S S件,而01背包每个物品有 1 1 1件。

我们是不是可以进行一个拆分,把这 S S S件物品拆成01背包的模型来计算。

我们再看物品的数量,假设最多的 100 100 100件物品,每个物品可以取 100 100 100次,我们使用三重循环的话,时间复杂度是 100 ∗ 100 ∗ 100 100 * 100 * 100 100100100,也就是 1 0 6 10^6 106。而C++程序一秒大概可以执行 1 0 7 ∼ 1 0 8 左 右 10^7 \sim10^8左右 107108的次数。

也就是说我们简单使用三重循环的暴力方法是可以过的。

#include <iostream>
using namespace std;

const int N = 110;

int dp[N];
int n,m;

int main()
{
   
    cin>>n>>m;
    
    for(int i = 1; i <= n; i++)
    {
   
        int v,w,s;//v 物品所占的体积 w 物品的重量 s 物品的数量
        cin>>v>>w>>s;
        
        for(int j = m; j >= v; j--)
            for(int k = 1; k <= s && (k * v <= j); k++)
                dp[j] = max(dp[j], dp[j - k * v] + k * w);
    }
    
    cout<<dp[m]<<endl;
    return 0;
}

多重背包 II

在这里插入图片描述

分析

看了一眼数据范围, 1000 × 2000 × 2000 1000 \times 2000 \times 2000 1000×2000×2000,直接暴力三重循环肯定超时了。
提示中显示,可以使用二进制优化的方法,也就是使用二进制对背包进行压缩。

我们直接把所有物品都变成一个一个的肯定不行,我们可以利用二进制独特的压缩方式,可以把多个物品压缩成一个物品,这样物品的数量压缩的范围简化到 1000 × l o g ( 2000 ) 1000 \times log(2000) 1000×log(2000),时间复杂度近似于 1000 × 11 × 2000 1000 \times 11 \times 2000 1000×11×2000,我们看这是可以在 1 S 1S 1S中跑完的,时间复杂度可以通过了。
在这里插入图片描述

#include <iostream>
#include <vector>
using namespace std;

const int N = 2010;

int n,m;
int dp[N];
//存放压缩后物品的仓库
typedef struct Good
{
   
    int v,w;
}Good;

int main()
{
   
    cin>>n>>m;
    vector<Good> goods;
    
    for(int i = 1; i <= n; i++)
    {
   
        int v,w,s;
        cin>>v>>w>>s;
        //状态压缩
        //10 -> 1 + 2 + 4 + 3
        for(int k = 1; k <= s; k *= 2)
      
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值