一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第25天,点击查看活动详情。
什么是01背包问题?
- 有N个物品,容量是V的背包,每个物品有两个属性,体积Vi,价值Wi
- 每件物品最多只能用一次,可以是0次或者1次
- 求:在背包能装的下的情况下的最大价值是多少
题目
概念
- 01背包问题:
N个物品,容量是V的背包,每个物品的属性包括:体积vi和价值wi,每件物品最多只能用一次 ,目标使得物品能被背包装得下,而且商品的最大价值达到最大 - 完全背包问题:每个物品有无限个,可以用无限次
- 多重背包问题:限制:每个物品最多有si个
- 优化版
- 朴素版
- 分组背包问题:物品有N组,每组里面有若干个物品,每组里面最多选一个
动态规划问题从两个角度考虑问题
- 状态表示 f(i, j)
- 要考虑一下,整个问题需要用几维的状态进行表示,每个状态的含义是什么
- 一般背包问题是两维的f(i, j)
- f(i, j)表示的集合是什么
- 所有选法(满足如下两个条件的所有选法)
- 条件
- 只从前i件物品里面选
- 选出来的物品的总体积<= j
- f(i, j)表示的集合的属性
- 属性一般有一下三种
- 最大值Max
- 最小值Min
- 元素的数量
- 属性一般有一下三种
- f(i, j)表示的集合是什么
- 一般背包问题是两维的f(i, j)
- 要考虑一下,整个问题需要用几维的状态进行表示,每个状态的含义是什么
- 状态计算(一般来说对应集合的划分)
- 如何一步步的将f(i, j)算出来
- 集合划分
- 概念:考虑一下如何将当前的集合划分成若干个更小的子集,使得每一个自己都可以算出来,都可以用前面更小的状态表示出来
- 原则:
- 不重复(求最大值的时候就可以重复,但是求个数一定不能重复)
- 不漏(一定要满足)
- 例子:以背包问题的集合划分为例
- 将f(i,j)表示的所有的选法分成两大类
- 选法中不含i的
- 什么意思:所有从1~i中选,总体积不超过j,并且不包含i,等价于从
1~i-1中选,总体积不超过j的最大值 - 表示:
**f(i - 1, j)** - 左边的最大值:
**f(i - 1, j)**
- 什么意思:所有从1~i中选,总体积不超过j,并且不包含i,等价于从
- 选法中含i的
- 由于直接求不好求,所以我们用间接的方式来求,例如,小明是第一名,那么全班同学都减20分,小明还是第一名,下面曲线救国的思路就跟上面这个例子类似
- 从1~i中选,总体积不超过j,而且所选的物品里面包含第i个物品,等价于从
1~i - 1中选物品,总体积不超过j - vi,最后再加上wi(第i个物品的价值) - 右边的最大值:
f(i - 1, j - vi) + wi - 注意:包含i的这种情况不一定存在,要加一个条件进行判断
if(j >= v[i])
- 然后
f(i, j) = Max(f(i - 1, j),** **f(i - 1, j - vi) + wi)
- 选法中不含i的
- 将f(i,j)表示的所有的选法分成两大类
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;
}
优化:一维的情况
- 发现:对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;
}