题目来源
题目描述
题目解析
(1)状态:
- dp[i][j] 表示前i个物品 放入 容量为 j 的背包,最多可以获得的价值
- a[…]为每个物品的大小,v为每个物品的价值
(2)方程
- dp[i][j] =
- 当
j
>
=
a
[
i
−
1
]
j >= a[i - 1]
j>=a[i−1],即空间够放下当前物品(只需要考虑当前物品),有两种决策:
- 不放,那么 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i - 1][j] dp[i][j]=dp[i−1][j]
- 放,那么: d p [ i ] [ j ] = d p [ i − 1 ] [ j − a [ i − 1 ] ] + v [ i − 1 ] ] dp[i][j] = dp[i - 1][j - a[i - 1]] + v[i - 1]] dp[i][j]=dp[i−1][j−a[i−1]]+v[i−1]]
- 从上面两个决策中选择一个:max{ d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − a [ i − 1 ] ] + v [ i − 1 ] ] dp[i - 1][j], dp[i - 1][j - a[i - 1]] + v[i - 1]] dp[i−1][j],dp[i−1][j−a[i−1]]+v[i−1]]}
- 当
j
<
a
[
i
−
1
]
j < a[i - 1]
j<a[i−1], 即空间不足以放下第 i 件物品,那么:
- d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i - 1][j] dp[i][j]=dp[i−1][j]
- 当
j
>
=
a
[
i
−
1
]
j >= a[i - 1]
j>=a[i−1],即空间够放下当前物品(只需要考虑当前物品),有两种决策:
- 为什么是 a[i - 1]] v[i - 1] ?
- 因为 第 i 件(最后一件物品)物品的下标为 i - 1
(3)初始化
- dp[i][0] = 0
- dp[0][j] = 0
(4)答案
- dp[n][m]
class Solution {
public:
int backPackII(int m, vector<int> &a, vector<int> &v) {
int n = a.size();
if(m == 0 || n == 0){
return 0;
}
// 表示前i个物品装入容量为j的背包的最大价值
std::vector<std::vector<int>> dp(n + 1, std::vector<int>(m + 1, 0));
for (int i = 0; i < n + 1; ++i) {
for (int j = 0; j < m + 1; ++j) {
if(i == 0 || j == 0){ // 前0个物品 || 背包承重为0时
dp[i][j] = 0; //最大价值为0(不要求刚刚装满背包)
}else if(j - a[i - 1] >= 0){ // 当前物品重量 能 放入背包j中
dp[i][j] = std::max(
dp[i - 1][j], // 不装:那么就是前i-1个物品装入容量为j的背包中的最大价值
dp[i - 1][j - a[i - 1]] + v[i - 1] // 装: 前i-1个物品装入容量为j - a[i - 1]的背包中的最大价值 + 当前物品的价值
);
}else{
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n][m];
}
};
背包问题
需要同时考虑两个维度:重量 + 价值。
- 所有的背包问题,先从重量入手,已经知道一个背包最大承重是M,不管怎么做装,每个装物品的方案的总重量都是0-M
- 如果对于每个总重量,我们能知道对应的最大价值是多少
(1)确定状态
- 需要知道N个物品
- 是否能拼出重量W(W = 0…M)
- 对于每个重量W,最大总价值是多少?
- 最后一步:最后一个物品(重量
A
N
−
1
A_{N-1}
AN−1,价值
V
N
−
1
V_{N-1}
VN−1)是否能装入背包
- 情况一:(能够装入但是/不能够装入)不进入,最大总价值不变。
- 情况二:(能够装入而且)选择装入,那么进入后,价值变为 V + V N − 1 V+V_{N-1} V+VN−1
- 上面两个决策中选择一个价值最大的
- 子问题:
- 要求前N个物品能不能拼出0,1,…M,以及拼出重量W能获得的最大价值
- 先要求前N-1个物品能不能拼出0,1,…M,以及拼出重量W能获得的最大价值
- 状态:
- f[i][j] 表示前 i 个物品装入大小为 j 的背包里, 可以获取的最大价值总和.
(2)转移方程
(3)初始条件&&边界情况
- 初始条件:
- f[0][0] = 0。0个物品可以拼出重量0,最大总价值是0
- f[0][1…M] = -1:0个物品不能拼出大于0的重量
- 边界情况:
(4)计算顺序
- 从小到大,从上到下
- 答案:dp数组中最大的那一个
滚动数组:
class Solution {
public:
int backPackII(int m, vector<int> &A, vector<int> &V) {
int n = A.size();
if(n == 0){
return 0;
}
std::vector<int> f (m + 1 , 0);
int i , w ;
f[0] = 0;
for (i = 1; i <= m; ++i) {
f[i] = -1;
}
for (i = 1; i <= n; ++i) { //对于每一个物品
for (w = m; w >= 0; --w) { //对于每一个重量
if(w >= A[i-1] && f[w - A[i - 1]] != -1){ //装得下&&之前有方案
f[w] = std::max(f[w], f[w - A[i - 1]] + V[i - 1]);
}
}
}
int res = 0;
for (i = 0; i <= m; ++i) {
if(f[i] != -1){
res = std::max(res, f[i]);
}
}
return res;
}
};
三种背包总结
背包问题状态转移方程的核心是:
- 分类 要第i件物品 与 不要第i件物品 两种情况
- 若可以挑多件,那就是分挑 0,1,2, …, k件那么多种情况。
- 01背包问题:
- N个物品,背包大小为B,每种物品仅有一件,可以取或者不取,第 i i i件物品的费用是 C i C_i Ci,价值是 W i W_i Wi
- 将哪些物品装入背包,可以使得这些物品耗费的费用总和不超过背包容量,而且价值总和最大
- 完全背包问题:
- N个物品,背包大小为B,每种物品无限件,可以取或者不取,第 i i i件物品的费用是 C i C_i Ci,价值是 W i W_i Wi
- 将哪些物品装入背包,可以使得这些物品耗费的费用总和不超过背包容量,而且价值总和最大
- 多重背包问题:
- N个物品,背包大小为B,每种物品最多有 M i M_i Mi件,可以取或者不取,第 i i i件物品的费用是 C i C_i Ci,价值是 W i W_i Wi
- 将哪些物品装入背包,可以使得这些物品耗费的费用总和不超过背包容量,而且价值总和最大
- 01背包问题:
class Solution {
// 01背包 Time ~ O(V)
// dp为动态数组,B为背包总大小,C为当前背包价值,W为当前背包重量
void zeroOnePack(vector<int> &dp, int B, int C, int W) {
for (int j = B; j >= C; --j) { // 对于每一个背包重量
dp[j] = std::max(dp[j], dp[j - C] + W); // 求最大价值 [不装, 装]
// dp[j] = dp[j] + dp[j - C]; // 求方案总数
}
}
// 完全背包(快) Time ~ O(V)
void completePack(vector<int> &dp, int B, int C, int W) {
for (int j = B; j >= C; --j) { // 对于每一个背包重量
dp[j] = std::max(dp[j], dp[j - C] + W);
// dp[j] = dp[j] + dp[j - C]; // 求方案总数
}
}
// 完全背包(慢)Time ~ O(V/C * V) 仅用来与快版本进行比较
void completePack2(vector<int> &dp, int B, int C, int W) {
for (int k = 0; k * C <= B; k++) { // 对于当前物品,能放入[0, 1, 2... k]件
zeroOnePack(dp, B, k * C, k * W); // 没一件可以转为01背包问题
}
}
// 多重背包(快) Time ~ O(logM * V)
void multiplePack(vector<int> &dp, int B, int C, int W, int M) {
int k = 1;
while (M > k) {
zeroOnePack(dp, B, k * C, k * W);
M = M - k;
k <<= 1;
}
zeroOnePack(dp, B, M * C, M * W);
}
// 多重背包(慢) Time ~ O(k * V) 仅用来与快版本进行比较
void multiplePack2(vector<int> &dp, int B, int C, int W, int M){
for (int k = 0; k <= M; k++) {
zeroOnePack(dp, B, k * C, k * W);
}
}
// -------
// 二维 01背包 Time ~ O(U * V)
// B为背包总承重
void zeroOnePack2D(vector<vector<int>> &dp, int B, int U, int C1, int C2, int W){
for (int j = B; j >= C1; --j) {
for (int k = U; k >= C2; --k) {
dp[j][k] = std::max(dp[j][k], dp[j - C1][k - C2] + W);
}
}
}
// 二维 完全背包 Time ~ O(U * V)
void completePack2D(vector<vector<int>> &dp, int B, int U, int C1, int C2, int W){
for (int j = C1; j <= B; j++) {
for (int k = C2; k <= U; k++) {
dp[j][k] = std::max(dp[j][k], dp[j - C1][k - C2] + W);
}
}
}
public:
// C为cost,W为重量weight、M为件数
int backPackII(int N, int B, vector<int> &C, vector<int> &W, vector<int> &M) {
// N为物品个数,B为背包总大小
std::vector<int> dp(B + 1);
// 初始化方式一:不要求刚好装满背包
for (int j = 0; j <= B; ++j) {
dp[j] = 0;
}
// 初始化方式二:要求刚好装满背包
dp[0] = 0;
for (int j = 1; j <= B; ++j) {
dp[j] = -1;
}
// 迭代计算dp
for (int i = 0; i <= N; ++i) { // 对于每一个物品
if (第i件物品属于01背包) {
zeroOnePack(dp, B, C[i - 1], W[i - 1]);
} else if (第i件物品属于完全背包) {
completePack(dp, B, C[i - 1], W[i - 1]);
} else if (第i件物品属于多重背包) {
multiplePack(dp, B, C[i - 1], W[i - 1], M[i - 1]);
}
}
// 返回最大价值
return dp[V];
}
};