01背包问题
介绍
01背包问题其实在我之前的博客中略有提及。01背包的问题描述大致如下:
有N
件物品和一个容量是V
的背包。每件物品只能使用一次。
第i
件物品的体积是
v
i
v_i
vi,价值是
w
i
w_i
wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
这是经典的动态规划问题,也是动态规划的入门背包问题。
DP解法
此前已经写过遗传算法解01背包问题,这次使用DP解法来求解01背包问题。
在DP问题中最重要的是求状态转移方程
,即如何将当前状态表示出来。此时我们可以尝试使用闫氏DP分析法:
- 将DP问题划分为两步:
状态表示
和状态计算
。 状态表示
中又分为两部分:状态表示的集合
是什么,以及状态表示的属性
是什么。- 状态计算则是求解状态转移方程的步骤
以01背包问题为例:
状态用f[i][j]
来表示。
- 状态表示
- 状态表示的
集合
:从前i
个物品中选,且总体积不超过j
的选法方案。 - 状态表示的
属性
:Max
(价值最大)
- 状态表示的
- 状态计算
f[i][j]
可分为两部分考虑:- 不选第
i
件物品
此时从前i
件物品中选且总体积不超过j
的选法方案 = 从前i - 1
件物品中选且总体积不超过j
的选法方案
即f[i][j] = f[i - 1][j]
。 - 选第
i
件物品
由于第i
件物品的体积为v[i]
此时从前i
件物品中选且总体积不超过j
的选法方案 = 从前i - 1
件物品中选且总体积不超过j - v[i]
的选法方案 + 第i
件物品的价值
即f[i][j] = f[i - 1][j - v[i]] + w[i]
- 不选第
由于f[i][j]
属性为Max
,因此需要从两种状态中取最大值,因此它的状态转移方程为
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
]
,
f
[
i
−
1
]
[
j
−
v
[
i
]
]
+
w
[
i
]
)
f[i][j] = max(f[i - 1][j],f[i - 1][j - v[i]] + w[i])
f[i][j]=max(f[i−1][j],f[i−1][j−v[i]]+w[i])
例题
题目描述
有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<v_i,w_i≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
解题思路
最主要是求出状态转移方程。
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N],w[N],v[N];
int n,m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
for (int i = 1; i <= n; i ++ ){ // 枚举第1 ~ 第n个物品
for (int j = 1; j <= m; j ++ ){ // 枚举第1 ~ 第m 个体积
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]; // 输出从前n个物品中选且总体积不大于m的最大价值
return 0;
}
代码的一维优化
for(int j = 1; j <= m; j ++)
循环内,只有j >= v[i]
时才有操作,因此可以直接优化为
for(int j = v[i]; j <= m; j ++)
,可以省去判断。- 观察代码可以发现,状态转移方程只用到了
f[i - 1]
和f[i]
两个状态,即第i
个物品只与第i - 1
个物品有关,因此我们可以将这一维优化掉,因为第i
个物品中f[i][j] = f[i - 1][j]
等价于f[j] = f[j]
.
f[i] = max(f[i - 1][j],f[i - 1][j - v[i]] + w[i])
也等价于f[j] = max(f[j],f[j - v[i]] + w[i])
,因为在第i
次循环到j
之前,f[j]
都还是f[i - 1][j]
,循环到j
之后,f[j] = f[i][j]
。
注意:如果j
还是从v[i]
循环到m
,那么当到第i
个物品时,f[j - v[i]]
将并不是f[i - 1][j - v[i]]
,而是f[i][j - v[i]]
,因为j
由小到大更新会先将f[i - 1][j - v[i]]
更新才更新f[i - 1][j]
,因此我们需要对j
进行从大到小循环。 v[i]
和w[i]
只在当前循环中用得上,我们可以只用v
,w
来代替v[i]
,w[i]
,优化空间复杂度。
优化后的代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1010;
int f[N];
int n,m;
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i ++){
int v,w;
cin >> v >> w;
for(int j = m; j >= v; j --)
f[j] = max(f[j],f[j - v] + w);
}
cout << f[m];
return 0;
}