01背包

动态规划 (01背包模型)

1、基本的背包模型

草稿-12-1

2、动态规划的理解方式

IMG_0113(20200510-200231)

2.1状态表示部分(抽象点)

背包模型基本表示成二维的:

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

动态规划需要用到的数据结构是数组,用来记忆之前的属性推出下一个的属性的过程

用数组的下标,记录当前的状态,一个状态表示的是当前状态所约束下的集合,

属性:

数组的值表示的是属性

属性有最大值最小值,和数量等,

属性是指当前状态下集合的属性,是基于当前状态的基础上的属性

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

01背包的约束条件

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

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

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

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

2.2状态计算部分(难点)

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

2.2.1将集合进行划分:

状态为:

1-i不包含i,容量为j

1-i包含i的,容量为j

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

表示的是,当可选择物品的数量改变的时候,对应j所有的不同情况,更新一遍集合的属性

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

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

改变前的集合属性是已知的,但是改变后的集合属性是未知的,也正是我们要求的

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

第一部分,所有选法都不包含新增物品

第二部分:所有选法都包含新增物品

如下图可知,当新增了一个物品i之后,选法就多了很多,有包含i的和没有包含i的

那么我们可以将新增的物品后的所有集合都将其分为两个部分,一个是包含i的选法,一个是不包含i的选法

动态规划

由上可知,不包含i的选法的属性是已知的,但是包含i的选法是未知的

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

  • 先求不包含i的选法的最大值(已知)
  • 求包含i的选法的最大值(未知,可求)
  • 取两个最大值的最大值(简单的对比)
2.2.2求包含i的选法的最大值

第一部分状态表示:从1~i不包含i的物品里面选,背包容量为 j-v[i]完整背包容量扣掉i物体的体积

第二部分状态表示:只选择第i个物品,背包容量刚好为i的体积

第一部分是将背包的容量拆分成一个刚好容下i的体积的容量

第二部分是剩下的容量存放其他物体的

动态规划

入上图可知,我们将一个方案拆分成两个部分,所以所有的方案数,也就是状态所表示的集合数量应应用乘法原理得到:第一部分的的方案数,乘以 第二部分的方案数

这个集合中方案的最大值,应该应用加法原理:第一部分方案的最大值 + 第二部分方案的最大值;

这时候所有都求出来了

二维代码:

#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

特点:

  • 每次都只用到要更新的上一层的值,之前的值都不会用到

  • 具体用到上一层的那个格子是不固定的,由上面的式子可以知道,是由新增物品的体积所决定的。

    但是,不管哪个格子用到的体积肯定比当前要更新的容积要小

f[j-v[i]] + w[i] 每次用到的都是当前要求的背包容量减去新增物品的体积

总结:

  • 特点1可得:每次都只用到上一层,那么我们可以用滚动数组来做

滚动数组:

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

由此可以将原本的二维数组变成一维数组

  • 特点2可得:不管哪个格子用到的体积肯定是比当前要更新的容积要小

要将j原本从小到大的遍历更新顺序变成从大到小

那么更新的时候就要保证两点:

第一点:用到的数据比当前要更新的数据要小

第二点:用到的数据是上一层的

解决第一点:因为这个部分是必须要拥有新增物品i的,所以背包容量至少要大于等于v[i]才行,所以遍历j的时候从v[i]开始,到m结束,这样就保证,j - v[i] <= j并且j - v[i] >=0

解决第二点:如果按照上一种情况的话就会遇到用到的数据不是上一层的,如下图

问题: 因为j是从小到大遍历的,而更新的时候要用到小的,就会使用到已经更新过的值

image-20200511131704605

所以修改一下方案,将j的遍历从大到小,这样既解决了第一点,又解决了第二点,如下图

image-20200511131445944

一维代码:

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值