1. 0-1背包问题
给定背包容量 C
,物品体积 volume[n]
,物品价值 value[n]
,每个物品只有一个。
求:在背包容量允许的情况下,装入背包物品的最大价值。
状态定义:
dp[i][j] 表示前 i 件物品放入容量为 j 的背包的最大价值。
边界条件:
转移方程:
一维数组优化(必须要逆序枚举)为什么不能正序循环呢?
在上述二维形式的状态转移方程中,很明显,(dp[i,j]) 依赖于数组的“上一行”。
在一维形式中,如果是正序,即 j=1...bag
, 如果 dp[5]
被更新, dp[9]
可能需要max(dp[9],dp[5])
, 但此dp[5]
不是二维数组形式的“上一行”,而是二维形式的“当前行”。
代码:
#include <iostream>
#include <vector>
using namespace std;
const int n = 5, bag = 8;
int value[] = {0, 4, 5, 2, 1, 3};
int volume[] = {0, 3, 5, 1, 2, 2};
int solve()
{
vector<int> dp(bag + 1, 0);
for (int i = 1; i <= n; i++)
{
for (int j = bag; j >= 1; j--)
if (j >= volume[i])
dp[j] = max(dp[j], dp[j - volume[i]] + value[i]);
}
for (int x : dp)
cout << x << ' ';
return dp[bag];
}
int main()
{
cout << solve() << endl;
}
2. 完全背包
有 (n) 种物品,每种物品单件质量为 w[i]
,价值为 c[i]
,现有容量为 C
的背包,假设每种物品都有无穷件,如何选取物品,使得装入背包的价值最大。
状态定义:
dp[i][j]表示前 i 件物品放入容量为 j 的背包的最大价值。
边界条件:
状态转移方程:
状态转移方程的含义:依据选择一个物品 (i) 放入来讨论,如果 (j<w[i]) 说明背包容量小于物品 (i) 的体积。如果 (j ge w[i]) ,可以分为以下 2 种情况:
- 不放入物品 (i),那么 (dp[i,j] = dp[i-1,j])。
- 放入物品 (i),那么 (dp[i,j] = dp[i,j-w[i]]+c[i])。
(dp[i,j-w[i]]+c[i]) 实际上是表示:无论背包中有没有物品 (i),我们都只考虑当前情况下,是否放入物品 (i)。
而在 01 背包问题中,(dp[i,j] = max(dp[i-1,j], dp[i-1,j-volume[i]]+value[i])),(dp[i-1,j-volume[i]]+value[i]) 表示在前 (i-1) 的物品已经选好的情况下,是否放入物品 (i) 。
二维数组写法:
const int n = 4, bag = 10;
int val[n + 1] = {0, 1, 3, 5, 9};
int vol[n + 1] = {0, 2, 3, 4, 7};
int completeKnapsack1()
{
vector<vector<int>> dp(n + 1, vector<int>(bag + 1, 0));
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= bag; j++)
{
if (j >= vol[i])
dp[i][j] = max(dp[i - 1][j], dp[i][j - vol[i]] + val[i]);
else
dp[i][j] = dp[i - 1][j];
}
}
return dp.back().back();
}
一维数组优化,cpp实现:
const int n = 4, bag = 10;
int val[n + 1] = {0, 1, 3, 5, 9};
int vol[n + 1] = {0, 2, 3, 4, 7};
int completeKnapsack()
{
vector<int> dp(bag + 1, 0);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= bag; j++)
{
if (j >= vol[i])
dp[j] = max(dp[j], dp[j - vol[i]] + val[i]);
}
}
return dp.back();
}
实际上,内层循环可以优化为:
for(int j=vol[i]; j<=bag; j++)
{
dp[j] = max(dp[j], dp[j-vol[i]]+val[i]);
}
注意到,在这里,内层循环是必须是从 1
到 bag
正序扫描的,这是与 01背包的不同之处。
从上述的二维形式的转移方程可以看出,(dp[i,j])依赖于其“上一行”的 (dp[i-1,j]) 以及“同一行”的 (dp[i,j-vol[i]])。如果是逆序扫描,那么 (dp[i,j-vol[i]]) 的值仍然是“上一行”的。