细说背包问题 - 01背包

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;
}
相关习题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值