背景
有若干个物品,每个物品有两个属性:价值和重量。现在考虑将这些物品装入一个容器,称为「背包」,背包只有一个属性:「最大承重」,每个物品只有一件。问如何选择物品,使得所选物品的重量总和在不超过背包的「最大承重」的前提下,这些物品的价值总和最大。
0-1背包
于每一个物品要么被选择放入背包,要么不放入背包,只有不选(用 0 表示)和选(用 1 表示)两种可能,因此称为「0-1 背包问题」。「0-1 背包问题」在「力扣」上没有对应的问题,我们将它形式化描述如下。
定义状态
一个一个物品考虑是否添加进背包,并且 背包有最大承重限制。根据「无后效性」的动态规划设计原则,将第一维设置成为考虑的物品的下标区间(前缀区间),从 00 开始到 N - 1N−1,第二维设置成背包重量,从 00 开始到 WW。
dp[i][j] 表示:考虑下标区间范围是 [0..i] 内的所有物品,且重量总和不超过 j 时,背包能装下物品的最大价值总和。
推导状态转移方程
对于下标为 i 的物品,有「选」和「不选」两种方案,比较这两种方案选出更好的。
下标为 i 的物品没有被选择时,问题转化成求物品的下标区间 [0.. i - 1] 里,选出重量总和不超过 j 的最大价值总和,即 dp[i - 1][j] 为所求,这里 i >= 1;
下标为 i 的物品被选择时,则问题转化成求物品的下标区间 [0.. i - 1] 里,选出重量总和不超过 j - w[i] 的最大价值总和,即 dp[i - 1][j - w[i]] + v[i] 为所求,这里 i >= 1,并且 j - w[i] >= 0 即 w[i] <= j ,也就是说,下标为 i 的物品的重量 w(i) 要 小于等于 当前考虑的背包承重 j ,状态转移才能发生。
整理一下,状态转移方程为:
dp[i][j]=max(dp[i−1][j],dp[i−1][j−w[i]]+v[i]),i≥1,j≥w[i]
代码
#include<iostream>
#include<math.h>
#define maxn 1010
using namespace std;
int main() {
int n = 0; // 物品个数
int mw = 0; // 最大重量
cin >> n >> mw;
int weights[maxn]; // 重量
int values[maxn]; // 价值
int dp[maxn][maxn];
for (int i = 0; i < n; ++i) {
cin >> weights[i] >> values[i];
}
// 给第一列赋值
for(int i = 1; i <= mw; i++) {
if(weights[0] <= i) {
dp[0][i] = values[0];
}
}
// 递推
for(int i = 1; i< n; i++) {
for(int j = 0; j <= mw; j++) {
dp[i][j] = dp[i-1][j];
if(weights[i] <= j) {
dp[i][j] = max(dp[i][j], dp[i-1][j-weights[i]] + values[i]);
}
}
}
cout << dp[n-1][mw] << endl;
return 0;
}
#include<iostream>
#include<math.h>
#define maxn 1010
using namespace std;
int main() {
int n = 0;
int mw = 0;
cin >> n >> mw;
int weights[maxn];
int values[maxn];
for (int i = 0; i < n; ++i) {
cin >> weights[i] >> values[i];
}
int dp[maxn][maxn];
for(int i = 1; i<= n; i++) {
for(int j = 0; j <= mw; j++) {
dp[i][j] = dp[i-1][j];
if(weights[i-1] <= j) {
dp[i][j] = max(dp[i][j], dp[i-1][j-weights[i-1]] + values[i-1]);
}
}
}
cout << dp[n][mw] << endl;
return 0;
}
#include<iostream>
#include<math.h>
#include<string.h>
#define maxn 1010
using namespace std;
int weights[maxn];
int values[maxn];
int dp[maxn];
int main() {
int n = 0;
int mw = 0;
cin >> n >> mw;
memset(dp, 0, sizeof(dp));
for (int i = 0; i < n; ++i) {
cin >> weights[i] >> values[i];
}
// 0-1背包优化
for(int i = 1; i <= n; i++) {
for(int j = mw; j >= 0; j--) {
if(weights[i-1] <= j) {
dp[j] = max(dp[j], dp[j-weights[i-1]]+values[i-1]);
}
}
}
cout << dp[mw] << endl;
return 0;
}
完全背包
有 N 种重量和价值分别为 wi 和 vi 的物品。从这些物品中挑选总重量不超过 W 的物品,求出挑选物品价值总和的最大值。每种物品可以挑选任意多件。
定义状态
与0-1背包一致
状态转移方程
dp[i][j]=max(dp[i−1][j],dp[i][j−w[i]])+v[i]
代码
#include <iostream>
#define maxn 1010
using namespace std;
int values[maxn];
int weights[maxn];
int dp[maxn][maxn];
int main() {
int n = 0;
int mw = 0;
cin >> n >> mw;
for(int i = 0; i < n; i++) {
cin >> weights[i] >> values[i];
}
for(int i = 1; i <= n; i++) {
for(int j = 0; j <= mw; j++) {
dp[i][j] = dp[i-1][j];
if(weights[i-1] <= j) {
dp[i][j] = max(dp[i][j], dp[i][j-weights[i-1]] + values[i-1]);
}
}
}
cout << dp[n][mw] << endl;
return 0;
}