解密01背包问题:如何在有限空间中实现最大价值?

01背包 - 问题分析

01背包是指在一个有容积限制(或者重量限制)的背包中放入物品,物品拥有体积、重量和价值等属性。需要求一种满足背包限制的放置物品的方式,使得背包中物品的价值之和最大。比如有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

在这里插入图片描述

在01背包问题中,每种物品仅有一件,可以选择放或不放。这是背包问题中最简单的一种,也是动态规划的经典问题之一。


  • 在下面的讲解中,我举一个例子:

背包最大重量为5.
物品为:
在这里插入图片描述

  • 问背包能背的物品最大价值是多少?

此时发现物品1 + 物品3 的重量为3 价值为14。 此时这是我们最大价值

  • 若背包恰好装满,求至多能装多大价值的物品是多少?

此时发现物品1 + 物品3 的重量为3 价值为14. 但是重量没有恰好装满,不符合题意.
我们在看物品2 + 物品3 的重量为5,价值为9. 重量恰好装满,且是我们这里的最大价值


01背包题目

【模板】01背包_牛客题霸_牛客网 (nowcoder.com)

动规五部曲分析:

  1. 分析状态表示
  2. 分析状态转移方程
  3. 初始化
  4. 填表顺序
  5. 返回值

第一问

  • 问背包能背的物品最大价值是多少

1. 状态表示

我们看看这个状态表示

dp[i] 表示: 从前 i 个物品中选,所有选法中,能挑选出来的最大价值.

相信很多同学第一时间是想出的是这个状态表示,但你们仔细想一下,我们的体积呢?这个状态表示体积可能会超出题目给定的范围,所有这个状态表示是错.我们必须换一种状态表示,我们换一个二维的数组,加多一维表示体积.

dp[i][j] 表⽰:从前 i 个物品中挑选,总体积「不超过J」 ,所有的选法中,能挑选出来的最⼤价值。

v表示体积,w表示价值

要时刻记着这个dp数组的含义,下面的一些步骤都围绕这dp数组的含义进行的,如果哪里看懵了,就来回顾一下i代表什么,j又代表什么。


2. 分析状态转移方程

  • 线性 dp 状态转移⽅程分析⽅式,⼀般都是根据「最后⼀步」的状况,来分情况讨论:
  1. 如果不放第i个物品的值

最后⼀步如果不选第 i 个物品.相当于就是去前 i - 1 个物品中挑选,并且总体积不超过 j
其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。

  1. 如果选择第i个物品

那么我就只能去前 i - 1 个物品中,挑选总体积不超过 j - v[i]的物品,。此时 dp[i][j] = dp[i - 1][j - v[i]] + w[i] 。因此需要特判⼀下 j >= v[i],因为要留⾜够的体积装这个第 i 个物品。综上,状态转移⽅程为: dp[i][j] = max(dp[i 1][j], dp[i - 1][j - v[i]] +w[i])

如果没看懂回顾一下状态表示, dp[i][j] 表⽰:从前 i 个物品中挑选,总体积「不超过J」 ,所有的选法中,能挑选出来的最⼤价值。

综上,状态转移⽅程为:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]) ,但需要特判一下 dp[i - 1][j - v[i]] + w[i])` 是否存在.


3. 初始化

  • 初始化的意义就是让我填表时候不越界,需要我们需要对边界条件处理一下.

我们多加⼀⾏,⽅便我们的初始化
在这里插入图片描述

  1. 第一个位置

i 是物品 ,从 0 号位置开始选,但是没有 0 号位置,因为物品是从 1 到 n的,所有填 0。

  1. 第一行

从 0 号位置开始选,容量不超过 0,此时仅需将第⼀⾏初始化为 0 即可。因为什么也不选,也能满⾜体积不⼩于 j 的情况,此时的价值为 0 。

在这里插入图片描述

  1. 我们讨论第 0 竖的情况,讨论 j,容量不超过 0,物品总是有体积,所以全部都不
    选,初始化成 0.
    在这里插入图片描述

4. 填表顺序

根据「状态转移⽅程」,我们仅需「从上往下」填表即可。


5. 返回值

我们看看状态转移⽅程
从前 i 个物品中挑选,最⼤价值总体积不超过 j,所有的选法中,能挑选出来的最⼤价值。根据「状态表⽰」,返回 dp[n][V] 。


第二问

  • 若背包恰好装满,求至多能装多大价值的物品是多少?

其实第⼆问仅需微调⼀下 dp 过程的五步即可。


1. 状态表示

还是跟之前一样

dp[i][j] 表⽰:从前 i 个物品中挑选,总体积正好等于 j ,所有的选法中,能挑选出来的最⼤价值。


2. 状态转移⽅程

  • 线性 dp 状态转移⽅程分析⽅式,⼀般都是根据「最后⼀步」的状况,来分情况讨论:

我们的dp[i-1][j]不一定存在 , 因为有可能凑不⻬ j 体积的物品,因此我们把不合法的状态设置为 -1 。不能等于0,因为0是代表没有物品选.

  • 其实分析情况跟第一问差不多,但是要注意细节
  1. 如果不放第i个物品的值

最后一步如果不选第i个物品.相当于就是去前 i - 1 个物品中挑选此时, 这个无需判断是否是-1,因为在dp[i][j]这种情况是一定存在的,所有我们的dp[i][j]可以先等于dp[i-1][j] .如果dp[i-1][j]是等于-1的话,那没关系,你选不了我也选不了,你从i-1个物品想凑成一个j,我这种情况也凑不成j,所有不用判断 dp[i][j] = dp[i -1][ j ]

  1. 如果选择第i个物品

在使⽤ dp[i][j - v[i]] 的时候,不仅要判断 j >= v[i] ,⼜要判断 dp[i][j -
v[i]] 表⽰的情况是否合法,也就是 dp[i][j - v[i]] != -1

综上,状态转移⽅程为:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]) ,但是在使⽤ dp[i - 1][j - v[i]] 的时候,要判断 j >= v[i] ,因为要留⾜够的体积装这个第 i 个物品。,⼜要判断 dp[i - 1][j - v[i]] 表⽰的情况是否合法,也就是 dp[i - 1][j - v[i]] != -1


3. 初始化

  • 初始化的意义就是让我填表时候不越界,需要我们需要对边界条件处理一下.

跟之前一样我们多加⼀⾏,⽅便我们的初始化
在这里插入图片描述

  1. 第一个位置

跟之前一样第一个物品是0的位置,容量为0,正好不选就可以了,所有初始化成0.
在这里插入图片描述

  1. 第一行

没有物品想凑成容量为1-n 那是不可能的,因为没有物品哪里的体积,那就是非法的,所有填-1
在这里插入图片描述

  1. 我们讨论第 0 竖的情况, 这个状态可以根据状态转移方程推导而出,当然判断条件j ≥ v[i] 为false不会进去

4. 填表顺序

根据状态转移⽅程,我们仅需从上往下填表即可。


5. 返回值

我们看看状态转移⽅程
从前 i 个物品中挑选,最⼤价值总体积不超过 j,所有的选法中,能挑选出来的最⼤价值。根据「状态表⽰」,返回 dp[n][V] 。但是由于最后可能凑不成体积为 V 的情况,因此返回之前需要「特判」⼀下。


C++运行代码

#include <iostream>
#include <string.h>
using namespace std;

const int N = 1010;
int n, V, v[N], w[N];
int dp[N][N];
int main()
{
    // 读⼊数据
    cin >> n >> V;
    for (int i = 1; i <= n; i++)
        cin >> v[i] >> w[i];

    // 解决第⼀问
    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= V; 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][V] << endl;


    // 解决第⼆问
    memset(dp, 0, sizeof dp);
    for (int j = 1; j <= V; j++) dp[0][j] = -1; //初始化


    for (int i = 1; i <= n; i++)
        for (int j = 0; j <= V; j++) // 修改遍历顺序
        {
            dp[i][j] = dp[i - 1][j];
            if (j >= v[i] && dp[i - 1][j - v[i]] != -1)
                dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);
        }

    //由于最后可能凑不成体积为 V 的情况,因此返回之前需要特判⼀下。
    cout << (dp[n][V] == -1 ? 0 : dp[n][V]) << endl;
    return 0;
}
  • 37
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 26
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值