01背包问题

183 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第25天,点击查看活动详情

什么是01背包问题?

  1. 有N个物品,容量是V的背包,每个物品有两个属性,体积Vi,价值Wi
  2. 每件物品最多只能用一次,可以是0次或者1次
  3. 求:在背包能装的下的情况下的最大价值是多少

题目

image.png

概念

  1. 01背包问题N个物品,容量是V的背包,每个物品的属性包括:体积vi价值wi,每件物品最多只能用一次 ,目标使得物品能被背包装得下,而且商品的最大价值达到最大
  2. 完全背包问题每个物品有无限个,可以用无限次
  3. 多重背包问题:限制:每个物品最多有si个
    1. 优化版
    2. 朴素版
  4. 分组背包问题:物品有N组,每组里面有若干个物品,每组里面最多选一个

动态规划问题从两个角度考虑问题

image.png

  1. 状态表示 f(i, j)
    1. 要考虑一下,整个问题需要用几维的状态进行表示,每个状态的含义是什么
      1. 一般背包问题是两维的f(i, j)
        1. f(i, j)表示的集合是什么
          1. 所有选法(满足如下两个条件的所有选法)
          2. 条件
            1. 只从前i件物品里面选
            2. 选出来的物品的总体积<= j
        2. f(i, j)表示的集合的属性
          1. 属性一般有一下三种
            1. 最大值Max
            2. 最小值Min
            3. 元素的数量
  2. 状态计算(一般来说对应集合的划分)
    1. 如何一步步的将f(i, j)算出来
    2. 集合划分
      1. 概念:考虑一下如何将当前的集合划分成若干个更小的子集,使得每一个自己都可以算出来,都可以用前面更小的状态表示出来
      2. 原则:
        1. 不重复(求最大值的时候就可以重复,但是求个数一定不能重复)
        2. 不漏(一定要满足)
      3. 例子:以背包问题的集合划分为例image.png
        1. 将f(i,j)表示的所有的选法分成两大类
          1. 选法中不含i的
            1. 什么意思:所有从1~i中选,总体积不超过j,并且不包含i,等价于从1~i-1中选,总体积不超过j的最大值
            2. 表示:**f(i - 1, j)**
            3. 左边的最大值:**f(i - 1, j)**
          2. 选法中含i的
            1. 由于直接求不好求,所以我们用间接的方式来求,例如,小明是第一名,那么全班同学都减20分,小明还是第一名,下面曲线救国的思路就跟上面这个例子类似
            2. 从1~i中选,总体积不超过j,而且所选的物品里面包含第i个物品,等价于从1~i - 1中选物品,总体积不超过j - vi,最后再加上wi(第i个物品的价值)
            3. 右边的最大值:f(i - 1, j - vi) + wi
            4. 注意:包含i的这种情况不一定存在,要加一个条件进行判断if(j >= v[i])
          3. 然后f(i, j) = Max(f(i - 1, j),** **f(i - 1, j - vi) + wi)

f(i, j)的含义:从前i个物品里面选,总体积不超过j的总价值最大的选法

代码

二维的写法

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n, m ; //n:物品的个数,m:背包的容量
int v[N], w[N]; // v:物品的体积; w:物品的价值
int f[N][N]; // 状态

int main()
{
    cin >> n >> m;
    
    for (int i = 1; i <= n; i++) cin >> v[i] >> w[i]; // 读入所有物品
    
    // f[0][1~m]:从0件物品里面挑选,价值是0,全局变量默认是0,所以就不用再写了
    // 从1开始枚举
    for (int i = 1; i <= n; i++)
        for (int j = 0 ; j <= m; j++)
        {
            f[i][j] = f[i - 1][j]; // 集合的左边
            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.png

  1. 发现:对f(i, j)来说,f(i)这一层只用到了f(i - 1)这一层,而且j层,只用到了j 跟j - vi,他们两者都是<=j的,没有说在j的两侧,所以我们可以用滚动数组来进行优化,下面看操作:
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n, m ; //n:物品的个数,m:背包的容量
int v[N], w[N]; // v:物品的体积; w:物品的价值
int f[N]; // 状态   // 修改1

int main()
{
    cin >> n >> m;
    
    for (int i = 1; i <= n; i++) cin >> v[i] >> w[i]; // 读入所有物品
    
    // f[0][1~m]:从0件物品里面挑选,价值是0,全局变量默认是0,所以就不用再写了
    // 从1开始枚举
    for (int i = 1; i <= n; i++)
        for (int j = m ; j >= v[i]; j--) // 修改3,j从v[i]开始循环,因为从0开始没有意义,if语句直接跳出来了,循环了很多次没有意义的if
        {                                // 从大到小枚举
            //f[j] = [j]; // 集合的左边  // 修改2,发现是一个恒等式,所以将它直接删掉计科
            f[j] = max(f[j], f[j - v[i]] + w[i]); // 修改3
        }
    
    cout << f[m] << endl;
    
    return 0;
    
}