01背包
解题
- 这道题无法用贪心求解。例如样例2,如果用贪心思路,应该先装第 1 件物品,这样就无法装其它物品了,只能得到 15 的价值。贪心题在这里
- 约定用
f[i][j]
表示将前 i 件物品 -> 容量为 j 的背包能够获得的最大总价值,分别用 w 和 v 表示第 i 种物品的重量和价值 - 如果
j < w
,则装不下第 i 件物品,这时候相当于将前 i - 1 件物品 -> 容量为 j 的背包,
f[i][j] = f[i - 1][j]
- 如果
j >= w
,能装得下第 i 件物品。如果选择装第 i 件物品,它的价值为 v[i] ,背包剩余容量为 j - w ,那么将前 i - 1 件物品 -> 容量为 j - w 的背包,获得的最大价值为f[i - 1][j - w] + v
,比较装与不装的最优结果是:
f[i][j] = max(f[i - 1][j], f[i - 1][j - w] + v)
- 将 n 件物品 -> 容量为 m 的背包,能够获得的最大总价值就是
f[n][m]
代码如下:
#include <bits/stdc++.h>
using namespace std;
int m, n;
int f[39][209];
int main() {
cin >> m >> n;
for (int i = 1; i <= n; i ++) {
int w, v;
cin >> w >> v;
for (int j = 1; j <= m; j ++) {
if (j < w) {
f[i][j] = f[i - 1][j];
}
else {
f[i][j] = max(f[i - 1][j], f[i - 1][j - w] + v);
}
}
}
cout << f[n][m];
return 0;
}
优化
- 和前一篇文章的优化思路一样,我们来看下面这条核心语句,看看如何将其中的二维数组 f 压缩成一维数组
f[i][j] = max(f[i - 1][j], f[i - 1][j - w] + v)
- 将二维数组 f 看成 n 行 m 列的矩阵,循环变量 i 是从小到大,第 i 行的元素可以通过已经计算好的第 i - 1 行的元素递推
- 赋值语句右边的
f[i - 1][j - w]
这一项,在第 j 列的左侧,因此压缩成一维数组时,循环变量 j 必须从大到小循环,才能避免更新后的元素对接下来赋值语句的影响 - 另外一条语句
f[i][j] = f[i - 1][j]
,当压缩成一维数组时,变成f[j] = f[j]
,可以省略 - 将 n 件物品 -> 容量为 m 的背包,能够获得的最大总价值就是
f[m]
优化后的代码,空间复杂度为o(n)
#include <bits/stdc++.h>
using namespace std;
int m, n;
int f[209];
int main() {
cin >> m >> n;
for (int i = 1; i <= n; i ++) {
int w, v;
cin >> w >> v;
for (int j = m; j >= w; j --) {
f[j] = max(f[j], f[j - w] + v);
}
}
cout << f[m];
return 0;
}
集装箱装载
除了上面一题的思路之外,这道题还有另外一种思路
解题
- 如果能够用前 i 件物品凑出重量 j,则将
f[j]
设为true
;如果不能,则将f[j]
设为false
。 - 如果因为选择了第 i 件物品才凑出重量 j,那么前 i - 1 件物品一定可以凑出重量 j - w,因此:
f[j] = f[j] | f[j - w]
- 初始化时,需要将
f[1] ~ f[m]
设为false
,因为还没有凑出这些重量;而f[0]
应该设为true
,因为在没有选择任何物品时,重量为 0 - 最后要找到将 n 件物品 -> 容量为 m 的背包能够获得的最大重量,应该从 m 到 1 枚举 j,如果
f[j] == true
,说明可以获得重量 j,不用再继续枚举了,程序结束
代码如下:
#include <bits/stdc++.h>
using namespace std;
int n, m;
bool f[40000];
int main() {
cin >> n >> m;
f[0] = true;
for (int i = 1; i <= n; i ++) {
int w;
cin >> w;
for (int j = m; j >= w; j --) {
f[j] |= f[j - w];
}
}
for (int j = m; j >= 1; j --) {
if (f[j] == true) {
cout << j;
return 0;
}
}
return 0;
}
换一个角度
- 如果在装第 i 件物品前,
f[j] == true
,那么装了第 i 件物品可以得到 j + w 的重量 - 这样递推有个前提,装第 i 件物品之前的重量不能超过 m - w,否则加上第 i 件物品之后,就超过了背包的容量
记住这个思路,在后面的题目中会用到这个方法
#include <bits/stdc++.h>
using namespace std;
int n, m;
bool f[40000];
int main() {
cin >> n >> m;
f[0] = true;
for (int i = 1; i <= n; i ++) {
int w;
cin >> w;
for (int j = m - w; j >= 0; j --) {
if (f[j]) {
f[j + w] = true;
}
}
}
for (int j = m; j >= 1; j --) {
if (f[j] == true) {
cout << j;
return 0;
}
}
return 0;
}