首先是01背包的基础理论,背包问题,即如何在有限数量的货物中选取使具有一定容量的背包中所装货物价值最大。使用动规五步曲进行分析,使用二维数组do[i][j]表示下标从0到i货物装在容量为j背包中的最大价值,dp[i][j]可由不放物品i(dp[i -1][j])与放置物品i(dp[i - 1][j - weight[i]])两个方向确定,可得递推公式为dp[i][j] = max(dp[i -1][j], dp[i - 1][j - weight[i]])。再对dp数组进行初始化,j为0时,背包里无法装入任何物品,最大价值dp[i][0] = 0,i为0时,在0号物品中进行选取,当j < weight[0]时,背包无法放入物品0,dp[0][j]=0,当j>=weight[0]时,背包里可以放入物品0,dp[0][j] = value[0]。dp数组其他位置初始化为0即可,因为这些位置的值是由dp[i - 1][j]与dp[i - 1][j - weight[i]]决定的。遍历顺序为先遍历物品,再遍历背包。
#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 j = 0; j < n; ++j){
cin >> value[j];
}
vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));
for (int j = weight[0]; j <= bagweight; j++){
dp[0][j] = value[0];
}
for (int i = 1; i < weight.size(); 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 - weight[i]] + value[i],
dp[i - 1][j]);
}
}
std::cout << dp[weight.size() - 1][bagweight] << std::endl;
}
int main() {
while(cin>> n >> bagweight){
solve();
}
return 0;
}
第二题是将01背包中的二维dp数组压缩为一维数组,将i-1层数据拷贝到当前层,将dp[j]压缩为dp[j]:容量为j的背包可以容纳物品的最大价值。dp[j] = max(dp[j], dp[j - weight[i]] + value[i]),由于dp[j]需要在自身与去掉物品i之间相比较,所以dp数组直接初始化为0即可。与二维dp数组不同的是,一维数组的背包遍历需要从后向前遍历,防止同一物品被多次取到。
#include <iostream>
#include <vector>
using namespace std;
int main(){
int m, n;
cin >> m >> n;
vector<int> costs(m);
vector<int> values(m);
for (int i = 0; i < m; i++){
cin >> costs[i];
}
for (int j = 0; j < m; j++){
cin >> values[j];
}
vector<int> dp(n + 1, 0);
for (int i = 0; i < m; i++){
for (int j = n; j >= costs[i]; j--){
dp[j] = max(dp[j], dp[j - costs[i]] + values[i]);
}
}
std::cout << dp[n] << std::endl;
return 0;
}
明显看到,一维dp数组的代码要比二维的简洁许多。
第三题是01背包的应用问题分割等和子集https://leetcode.cn/problems/partition-equal-subset-sum/description/,关于这道题为何能用01背包解决,卡哥在代码随想录中做出了如下解释:利用动规五步曲进行分析可得:当dp[sum/2] = sum/2时,即可找到满足题意的集合。dp[j] = max(dp[j],dp[j - nums[i]] + nums[i])。遍历方式与初始化均与01背包相同。
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 == 1) return false;
int target = sum/2;
vector<int> dp(10001, 0);
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;
}
};