01背包问题
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 v i v_i vi,价值是 w i w_i wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N行,每行两个整数 v i v_i vi, w i w_i wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0
<
N
,
V
≤
1000
0<N,V≤1000
0<N,V≤1000
0
<
v
i
,
w
i
≤
1000
0<vi,wi≤1000
0<vi,wi≤1000
本文使用闫氏DP分析法——通过集合的角度来分析DP问题
分析步骤:
第一步:状态表示
这一步需要求出:
1、集合定义
2、集合里的元素的条件
3、集合元素的属性(Min/Max/Count)
4、f[i][j]具体含义
第二步:状态计算
这一步需要求出:
1、划分集合,根据最后一个不同点,最后一个有区分度的地方,将一个集合化为几个子集,
每个子集代表一类方案,划分时做到不重不漏
如果最后一个没有区分度,那就看 倒数第二个
2、找到每个子集的状态转移公式,如何通过子集转移到上一层的集合
第三步:确定边界
根据我们这个状态的含义,它的值应该是什么?怎么去初始化它,可以先画表格理解
详细步骤
先给出分析完之后的思维导图
第一步状态表示:
集合定义:
f[i][j]既表示每种状态也表示每一个集合
因为一共有2个不同的维度,第1个是每一个物品,第2个是每个物品的体积,
所以这就提示我们可以使用二维维度f[i,j]表示所有选法的集合
集合里的元素的条件:
1、从前i个物品中选
2、总体积<=j
所以结合定义和条件,f[i][j]具体含义:f[i][j]表示从前i个物品中选,且总体积<=j,的所有选法的集合
集合元素的属性:
f[i][j]存的是一个数,这个数是集合的某种属性(Min/Max/Count),
所以f[i][j]表示从前i个物品中选,且总体积<=j 的 所有选法 的价值最大值
接下来的集合划分、状态转移、边界处理都是从f[i][j]的含义出发来解决
第二步状态计算:
由题意结合f[i][j]的具体含义,f[N][V]就是我们要的答案,但f[i][j]所表示的状态如何被计算出来,通过集合划分
集合划分:
把f[i][j]表示的集合划分为若干个子集,使得每个子集都可以由前面的更小的子集表示,
每个子集代表一类选择方案
我们要求的是 每个子集(每种选法)的最大价值,这个最大价值就是所有从前i个物品中选且总体积<=j,
的所有方案的最大价值
划分技巧:
找到f[i][j]标识的所有方案里面,最后一个不同点,最后一个有区分度的地方,
在f[i][j]中最后一个有区分度的地方应该是最后一个物品如何选择:选择或不选择第i个物品
划分原则:
不重(求最值可以不满足)
不漏(一定要满足)
结合以上三点,将f[i][j]划分为两个子集:
第1个子集:
从前i个物品中选,不包含第i个物品,且总体积<=j的 所有选法的 集合
等价于从前i-1个物品中选总体积<=j的所有选法的集合
对应于f[i-1][j]
第2个子集:
从前i个物品中选,包含第i个物品,且总体积<=j的 所有选法 的集合
这个子集不能直接求,通过曲线救国的方式:
第一步:先假设所有方案都是包含第i个物品的,然后将所有方案同时去掉第i个物品,则物品数量
剩下i-1个物品,体积变为j-v[i](总体积减去第i个物品的体积),对应于f[i-1][j-v[i]]
第二步:在第一步的基础上,选上第i个物品,
就是在选i-1个物品的最大价值的基础上加上第i个物品的价值,对应于f[i-1][j-v_i]+w[i]
注意:当体积j装不下第i个物品时,即(j < v[i]),此时没得选,为空集
所以状态转移公式为:
if (j >= v[i])
f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
else
f[i][j] = f[i - 1][j];
第三步确定边界:
由f[i - 1][j],i需要>=1,所以需要确定一下f[0,0]~f[0,m]的值
因为f[0][0]表示一件物品都不选的最大价值,显然就是0
所以f[0,0]~f[0,m]=0
最后 f[N][V] 就是要找的答案
代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int ARRMAX = 1000 + 10;
// 物品数量 和 背包容积
int N, V;
// 每个物品体积 和 价值
int v[ARRMAX], w[ARRMAX];
// dp数组
int f[ARRMAX][ARRMAX];
int main()
{
cin >> N >> V;
for (int i = 1; i <= N; i++)
{
cin >> v[i] >> w[i];
}
// 枚举所有状态f[0~n,0~m]
// 首先处理边界问题
// 因为f[0,0]~f[0,m]表示一件物品都不选的 且 总体积不超过j的 所有选法 的集合
// 当然这里开的全局数组,每个元素默认初始化为0,所以下面的for循环初始化可以不要
for (int i = 0; i <= N; i++)
{
f[0][i] = 0;
}
for (int i = 1; i <= N; i++)
{
for (int j = 0; j <= V; j++)
{
// 如果体积j能装下第i个物品(右边集合不为空集)
if (j >= v[i])
{
// 对左右两边集合的最大值求max
f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
}
else {
// 左边的集合,当前方案的最大值
f[i][j] = f[i - 1][j];
}
}
}
cout << f[N][V] << endl;
return 0;
}