文章链接:二维背包问题、一维背包问题、416. 分割等和子集
视频链接:二维背包问题、一维背包问题、416. 分割等和子集
题目链接:背包问题、416. 分割等和子集
背包问题理论基础
背包问题分类:
01 背包(二维)
思路:
1.dp[i][j]数组的含义:任取[0, i]的物品放入容量为 j 的背包里的最大价值;
2.递推公式:①不放物品i:dp[i][j] = dp[i - 1][j]; ②放物品 i : dp[i][j] = dp[i - 1][j - weight[i]] + val[i];
综上,递推公式是 :dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + val[i]);
3.初始化数组:
dp[i][0],无论是选取哪些物品,背包价值总和一定为0。
dp[0][j],当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。
当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。
#include <bits/stdc++.h>
using namespace std;
int n, bagweight;
void solve() {
vector<int> weight(n, 0);
vector<int> value(n, 0);
for (int i = 0; i < n; i++) {
cin >> weight[i];
}
for (int i = 0; i < n; i++) {
cin >> value[i];
}
vector<vector<int>> dp(n, vector<int>(bagweight + 1, 0));
// 初始化
// 当j大于等于weight[0]的值初始化为value[0]
// 其余的已经在上面初始化为0
for (int j = weight[0]; j <= bagweight; j++) {
dp[0][j] = value[0];
}
// 递推公式
for (int i = 1; i < n; i++) {
for (int j = 0; j <= bagweight; j++) {
if (j < weight[i]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
cout << dp[n - 1][bagweight] << endl;
}
int main() {
while (cin >> n >> bagweight) {
solve();
}
return 0;
}
01 背包(一维、滚动数组)
思路:
1.dp[i][j]数组的含义:dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。
2.递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
3.初始化数组:下标为0的值为0;而对于下标不为0的值,为了让dp数组在递归公式的过程中取的最大的价值,而不被初始值覆盖,只需要将其初始值也设置为0就行了。
注意:
两个循环不能交换顺序,为什么?
如果交换了顺序,就是一直循环第一个物品了。(可以自己模拟试试)
#include <bits/stdc++.h>
using namespace std;
int main() {
int m, n;
cin >> m >> n;
std::vector<int> weight(m); // 重量
std::vector<int> value(m); // 价值
for (int i = 0; i < m; i++) {
cin >> weight[i];
}
for (int j = 0; j < m; j++) {
cin >> value[j];
}
std::vector<int> dp(n + 1, 0);
for (int i = 0; i < m; i++) { // 物品
for (int j = n; j >= weight[i]; j--) { // 容量
// 为什么从后向前遍历?
// 答:倒序遍历是为了保证物品i只被放入一次!
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
cout << dp[n] << '\n';
return 0;
}
416.分割等和子集
思路:
如何将该问题抽象成01背包问题?
背包的容量就是数组和的一半;只要能找出一个数组的和等于这个背包的容量就是返回true,否则就是false。
该背包问题中的重量和价值分别是什么?
重量和价值都是所给数组的值,也就是说二者是等价的。这也进一步说明了dp[target] = target;就是true的判定条件。
1.dp[i]数组的含义:表示容量为i时,背包里的最大价值的值。
2.递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
3.初始化数组:都为0;
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum = 0;
vector<int> dp(10001, 0);
for (int i = 0; i < nums.size(); i++) {
sum += nums[i];
}
if (sum % 2 == 1) return false;
int target = sum / 2; // 背包容量
// 开始01背包
for (int i = 0; i < nums.size(); i++) { // 物品
for (int j = target; j >= nums[i]; j--) {
// 背包的容量(容量要大于你要放的这个物品的重量)
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
}
}
if (dp[target] == target) return true;
return false;
}
};