01背包

本文深入探讨了动态规划在01背包问题中的应用,详细阐述了状态表示和状态计算两个核心部分。状态表示通过二维数组dp[i][j]来描述,其中i表示物品数,j表示背包容量。状态计算中,通过状态转移方程dp[i][j]=max(dp[i-1][j], dp[i-1][j-v[i]]+w[i])实现,优化后使用一维数组并从大到小遍历j。最后给出了完整的一维代码实现。
摘要由CSDN通过智能技术生成

动态规划 (01背包模型)

1、基本的背包模型

草稿-12-1

2、动态规划的理解方式

IMG_0113(20200510-200231)

通常可以将 动态规划(DP) 问题分为两个部分进行理解:1. 状态表示。2. 状态计算

2.1状态表示(抽象点)

背包模型,可以表示成二维的

  • 一个表示在哪几个物品里面选
  • 一个表示背包的体积多少

如: dp[i][j] 其中的 i 表示为从i个物品中选择。j表示选择的物品的题解不超过j

动态规划需要用到的数据结构是数组,用来记忆之前的属性从而推出下一个的属性的过程。用数组的下标,记录当前的状态,一个状态表示的是当前状态所约束下的集合,

**属性:**数组的值表示的是属性。例如:最大值最小值,和数量等,属性是指当前状态下集合的属性,是基于当前状态的基础上的属性。

如:dp[i][j] 表示的状态是从前 i 个物品中选择,总体积小于等于 j 的所有选择方案的集合。而 dp[i][j]的值本身表示的是这个状态下集合中的属性

正如上面所说,集合是受状态约束的,约束的变量是数组的两个下标,不同的题目“约束的条件”不一样

01背包的约束条件

  1. 只从前i个物品里面选
  2. 总体积<= j

约束条件就好像一个函数,是随着题目不同而确定下来的

  • 状态:数组的下标就是自变量
  • 状态改变 ——>集合元素改变——>属性改变

状态是最根本的,状态的改变决定了集合的范围,以及属性的值

2.2状态计算部分(难点)

状态计算的过程:通过已知的部分集合的属性求出未知的集合的属性

2.2.1将集合进行划分:

首先,集合的改变只受状态(自变量)改变的影响,所以当i改变时,也就是可选择物品的数量增加时,要遍历一遍 j 的所有不同的取值。

表示的是,当可选择物品的数量改变的时候。相同的背包体积,可存储的物品选择也会发生改变。从而引起集合个数的改变,因为集合的状态改变了,对应的属性也可能会发生相应的改变。因此要更新状态。

所以定义双重循环遍历所有的状态:

for(int i = 1; i <= n; i++)
    for(int j = 0; j <= m; j++)
    {
     	/**
         *占位符
         **/
    }

可选择物品的数量改变前的集合属性是已知的,但是改变后的集合属性是未知的。而我们要做的就是通过计算,根据已知的集合属性求出改变后的集合属性。

将改变后的集合分为两个部分:

​ 第一部分,未新增物品前的集合

​ 第二部分:新增物品后,新增出来的集合

如下图可知,当新增了一个物品i之后,就会新增很多种选择,新增的选择有一个特点,就是包含新增的物品 i

可以将集合都分为两个部分,一个是包含i的选法即新增的部分,一个是不包含i的选法即原本的部分

image-20210412220552751

由上可知,不包含i的选法的属性是已知的 dp[i-1][j],但是包含i的选法是未知的

由上图可知,如果要求新增物品i之后的所有选法的最大值,我们可以曲线救国:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hARTuFWi-1618240400088)(https://images.21hut.com/uPic/image-20210412221242578.png)]

2.2.2求包含i的选法(新增集合)的最大值

新增集合即包含 i 物品的集合有一个特点,就是每一种组合都固定包含物品 i

可以将集合中的每个组合拆分成两个部分:

​ 第一部分:物品 i

​ 第二部分:除了物品 i 的其他物品

image-20210412222129311

如上图可知,

我们将一个方案拆分成两个部分,所以所有的方案数,也就是状态所表示的集合数量:

应用乘法原理得到:第一部分的的方案数,乘以 第二部分的方案数

这个集合中方案的最大值:

应用加法原理:第一部分方案的最大值 + 第二部分方案的最大值;

即可以推出:dp[i][j] = max(dp[i-1][j],dp[i-1][j-v[i]] + w[i]); 的状态计算公式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tFibe7eh-1618240400090)(https://images.21hut.com/uPic/image-20210412223914728.png)]

二维代码:

#include<iostream>
using namespace std;
const int N = 10010;

int v[N],w[N];
int f[N][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++)
        {
            //第一部分 不包含i  容量为j
            f[i][j] = f[i-1][j];
            //第二部分 只包含i的  容量为j
            //		再拆分成两部分, 不包含i 容量为j-v[i]
            //					  只有i 容量为V[i]
            if(j >= v[i])f[i][j] = max(f[i][j],f[i-1][j-v[i]] + w[i]);
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

优化代码,二维变一维

思路

二维不断更新的过程

image-20200511130605768

**特点:**需要用到的状态值是dp[i-1][j-v[i]]

其中 i 值用到了 i - 1,而 j 值用到了 j-v[i]

总结:

注意点 1:因为i只用到了i-1 那么可以用滚动数组来做

滚动数组:

举例: 要求b要用到a,那么当用a求出b的时候,就可以把当前b的值当成是a用来再求b+1的值

注意点二:j 只用到了j-v[i] 当要更新 j 的时候只会用到体积j-v[i] 的状态。

加上上面已经将二维数组转换成一维的,要将j原本从小到大的遍历更新顺序变成从大到小

原因:

image-20210412225851182

一维代码:

#include<iostream>
using namespace std;
const int N = 10010;

int v[N],w[N];
int f[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--)
            f[j] = max(f[j],f[j-v[i]] + w[i]);
    cout<<f[m]<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值