0-1背包
有 n
件物品和一个最多能背重量为 w
的背包。第i件物品的重量是 weight[i]
,得到的价值是 value[i]
。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
二维数组
对于第 i
件物品,只有不放入背包和放入背包两个选择;不放入背包,背包容量不变;放入背包,背包容量减小。
所以需要两个维度,分别表示背包容量和物品;定义二维数组为 dp[i][j]
表示前 i
件物品放入背包中的价值总和;i
来表示物品、j
表示背包容量;
-
当背包容量
j
小于第i
件物品的重量时,此时价值总和就是前i - 1
件物品中放入背包的价值总和dp[i - 1][j]
; -
当背包容量
j
大于第i
件物品的重量时,
2.1. 第i
件物品不放入背包;此时价值总和就是前i - 1
件物品中放入背包的价值总和:dp[i - 1][j]
;
2.2. 第i
件物品放入背包;此时价值总和就是前i - 1
件物品中放入背包(剩余容量j - w[i]
)的价值总和加上第i
件物品的价值:dp[i - 1][j - w[i]] + v[i]
;最后,前
i
件物品放入背包中的价值总和为:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i])
;
例子:
背包可容纳重量 W = 6;
物品 | 重量w | 价值v |
---|---|---|
1 | 2 | 3 |
2 | 4 | 1 |
3 | 3 | 5 |
4 | 1 | 2 |
5 | 4 | 3 |
dp table
- | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 3 | 3 | 3 | 3 | 3 |
2 | 0 | 0 | 3 | 3 | 3 | 3 | 4 |
3 | 0 | 0 | 3 | 5 | 5 | 8 | 8 |
4 | 0 | 2 | 3 | 5 | 7 | 8 | 10 |
5 | 0 | 2 | 3 | 5 | 7 | 8 | 10 |
最后,dp[5][6] = 10
,即前5
件物品放入背包中的最大价值为10
;
一维数组
递推公式:dp[j] = max(dp[j], dp[j - w[i]] + v[i])
;
在遍历背包时要倒序遍历,因为正序遍历会让一个物品被多次放入;
比如:第 1 个物品,在背包容量为 4 的时候,被放入两次;
- | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
初始状态0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 3 | 3 | 6 | 0 | 0 |
… |
46. 携带研究材料
卡码网46. 携带研究材料
题目描述
小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据不同的空间,并且具有不同的价值。小明的行李空间为 N,问小明应该如何抉择,才能携带最大价值的研究材料,每种研究材料只能选择一次,并且只有选与不选两种选择,不能进行切割。
输入描述 第一行包含两个正整数,第一个整数 M 代表研究材料的种类,第二个正整数 N,代表小明的行李空间。
第二行包含 M 个正整数,代表每种研究材料的所占空间。
第三行包含 M 个正整数,代表每种研究材料的价值。输出描述 输出一个整数,代表小明能够携带的研究材料的最大价值。
输入示例 6 1 2 2 3 1 5 2 2 3 1 5 4 3
输出示例 5
提示信息 小明能够携带 6 种研究材料,但是行李空间只有 1,而占用空间为 1 的研究材料价值为 5,所以最终答案输出 5。数据范围: 1 <= N <= 5000 1 <= M <= 5000 研究材料占用空间和价值都小于等于 1000用
二维数组
#include <iostream>
#include <vector>
using namespace std;
int package(int& m, int& n, vector<int>& weight, vector<int>& value) {
vector<vector<int>> dp(m + 1, vector<int>(n + 1));
for (int i = 1; i < m + 1; i++) {
for (int j = 0; j < n + 1; j++) {
if (j < weight[i-1]) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]);
}
}
}
// for (int i = 0; i < m + 1; i++) {
// for (int j = 0; j < n + 1; j++) {
// cout << dp[i][j] << " ";
// }
// cout << endl;
// }
return dp[m][n];
}
int main() {
int m; // 材料种类
int n; // 行李空间
// m = 6; n = 1;
cin >> m >> n;
vector<int> weight(m); // 每种研究材料的所占空间
vector<int> value(m); // 每种研究材料的价值
for(int i = 0; i < m; ++i) {
cin >> weight[i];
}
for(int j = 0; j < m; ++j) {
cin >> value[j];
}
int res = package(m, n, weight, value);
cout << res << endl;
return 0;
}
一维数组
int package(int& m, int& n, vector<int>& weight, vector<int>& value) {
vector<int> dp(n + 1);
for (int i = 1; i < m + 1; i++) {
for (int j = n; j >= weight[i - 1]; j--) { // 逆序
dp[j] = max(dp[j], dp[j - weight[i - 1]] + value[i - 1]);
}
}
//for (int j = 0; j < n + 1; j++) {
// cout << dp[j] << " ";
//}
//cout << endl;
return dp[n];
}
416. 分割等和子集
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
将每一个子集都看作一个背包,每一个数字都有放入和不放入两种选择,想让子集和等于一半的所有元素和,所以背包容量取总元素和的一半;
因为元素都是正整数,所以每个子集和必须时正整数,对于总元素和为奇数时,不满足子集和为正整数,即不存在将数组分割成两个子集,且两个子集的元素和相等;
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
}
if (sum % 2 != 0) {
return false;
}
int m = nums.size() + 1;
int n = sum / 2 + 1;
vector<int> dp(n);
for (int i = 1; i < m; i++) {
for (int j = n - 1; j >= nums[i - 1]; j--) {
dp[j] = max(dp[j], dp[j - nums[i - 1]] + nums[i - 1]);
}
}
// for (int j = 0; j < n; j++) {
// cout << dp[j] << " ";
// }
// cout << endl;
return dp[n - 1] == sum / 2;
}
};