一、01背包问题
题目描述
有N件物品和一个容量是V的背包。每件物品只能使用一次。
第i件物品的体积是viv_ivi,价值是wiw_iwi
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wivi,wivi,wi,用空格隔开,分别表示第 i件物品的体积和价值。
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例
8
解题步骤
1 状态表示:确定问题的状态
状态表示dp[i][j]dp[i][j]dp[i][j],表示考虑装前i个物品,当背包容量为j时,背包内所有物品总价值的最大值。
题目所求的背包价值最大值为dp[N][V]dp[N][V]dp[N][V]
2 状态计算
当第i个物品的体积viv_ivi大于j时,背包装不下第i个物品。
dp[i][j]=dp[i−1][j]dp[i][j] = dp[i-1][j]dp[i][j]=dp[i−1][j]
当第i个物品的体积viv_ivi小于等于j时,我们可以装第i个物品,也可以不装第i个物品。
若装第i个物品,该状态由装前i-1个物品的状态推广而来。当第i个物品装入背包时,背包容量减少v[i],背包价值增加w[i],所以状态方程为
dp[i][j]=max(dp[i−1][j],dp[i−1][j−v[i]]+w[i])dp[i][j] = max(dp[i-1][j], dp[i-1][j - v[i]] + w[i])dp[i][j]=max(dp[i−1][j],dp[i−1][j−v[i]]+w[i])
3 初始化
当i=0时,无论背包容量为何值,背包内没有物品,总价值为0。
#include <iostream>
using namespace std;
const int N = 1010;
int v[N];
int w[N];
int dp[N][N];
int main()
{
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++)
scanf("%d %d", &v[i], &w[i]);
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= m; j ++)
{
if(j - v[i] >= 0)
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);
else
dp[i][j] = dp[i - 1][j];
}
}
for(int i = 1; i <= n; i ++)
{
for(int j = 1; j <= m; j ++)
{
printf("%d ", dp[i][j]);
}
printf("\n");
}
cout << dp[n][m];
return 0;
}
DP优化
观察二维的动态规划转移方程,我们可以发现两个关键点:
- f[i][...]只依赖于f[i−1][...]:也就是说,当前第i层的状态只依赖于第i−1层的状态,而与更早的层(如i−2,i−3等)无关。这意味着我们不需要保存所有i的历史信息,只需要保存上一层的信息即可。f[i][...] 只依赖于 f[i - 1][...]:也就是说,当前第 i 层的状态只依赖于第 i-1 层的状态,而与更早的层(如 i-2, i-3 等)无关。这意味着我们不需要保存所有 i 的历史信息,只需要保存上一层的信息即可。f[i][...]只依赖于f[i−1][...]:也就是说,当前第i层的状态只依赖于第i−1层的状态,而与更早的层(如i−2,i−3等)无关。这意味着我们不需要保存所有i的历史信息,只需要保存上一层的信息即可。
- f[i][j]依赖于f[i−1][j]和f[i−1][j−v[i]]:具体来说,f[i][j]依赖于上一层的j和j−v[i]位置的值。这两个位置都在当前j的左侧(即j−v[i]<=j)f[i][j] 依赖于 f[i - 1][j] 和 f[i - 1][j - v[i]]:具体来说,f[i][j] 依赖于上一层的 j 和 j - v[i] 位置的值。这两个位置都在当前 j 的左侧(即 j - v[i] <= j)f[i][j]依赖于f[i−1][j]和f[i−1][j−v[i]]:具体来说,f[i][j]依赖于上一层的j和j−v[i]位置的值。这两个位置都在当前j的左侧(即j−v[i]<=j)。
基于以上两点,我们可以将二维数组优化为一维数组:
状态表示:用 f[j] 表示总体积不超过 j 时的最大价值。
状态计算:在更新 f[j] 时,我们需要的是上一层的 f[j] 和 f[j - v[i]] 的值。
为什么需要从后往前更新?
关键在于如何保证在更新 f[j] 时,f[j - v[i]] 仍然是上一层的值(即未被当前层的更新所覆盖)。
如果从前往后更新:假设我们正在处理第 i 个物品,j 从小到大遍历。当更新 f[j] 时,f[j - v[i]] 可能已经被当前层的更新覆盖(即已经用第 i 个物品的信息更新过),这样就会导致重复计算(即同一个物品被多次选取),这与01背包问题中每个物品只能选一次的条件矛盾。
而从后往前更新可以避免这个问题:
在j 从大到小遍历(从 V 到 v[i])的过程中,在更新 f[j] 时,f[j - v[i]] 还没有被当前层的更新覆盖,仍然是上一层的值,因此可以正确反映“不选当前物品”和“选当前物品”的两种情况。
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
int v[N], w[N];
int f[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= v[i]; j -- )
f[j] = max(f[j], f[j - v[i]] + w[i]);
cout << f[m] << endl;
return 0;
}
二、分组背包问题
题目描述
有 N组物品和一个容量是 V的背包。
每组物品有若干个,同一组内的物品最多只能选一个。每件物品的体积是 vijv_{ij}vij,价值是 wijw_{ij}wij,其中 i 是组号,j是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。
接下来有 N组数据:
每组数据第一行有一个整数 SiS_iSi,表示第 i 个物品组的物品数量;每组数据接下来有 SiS_iSi 行,每行有两个整数 vijv_{ij}vij,wijw_{ij}wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输入样例
3 5
2
1 2
2 4
1
3 4
1
4 5
输出样例:
8
解题步骤
分组背包问题是01背包问题的扩展,其特点是:
- 物品被分为若干组,每组内有若干物品。
- 每组中只能选一个物品或不选(即每组最多选一个物品)。
1 状态表示:确定问题的状态
状态表示dp[i][j]dp[i][j]dp[i][j],表示考虑装前i组物品且每组物品最多只装一个时,当背包容量为j时,背包内所有物品总价值的最大值。
题目所求的背包价值最大值为dp[N][V]dp[N][V]dp[N][V]
2 状态计算
对于第 i 组物品,我们有以下选择:
-
不选该组的任何物品:状态直接从 dp[i-1][j] 转移而来。
-
选该组的某个物品 k(假设该组有 S 个物品):该物品的体积为 v[i][k],价值为 w[i][k]。
则状态转移方程:dp[i][j]=max(dp[i−1][j],max1≤k≤S(dp[i−1][j−v[i][k]]+w[i][k]))dp[i][j]=\max\left(dp[i-1][j],\quad\max_{1\leq k\leq S}(dp[i-1][j-v[i][k]]+w[i][k])\right)dp[i][j]=max(dp[i−1][j],1≤k≤Smax(dp[i−1][j−v[i][k]]+w[i][k]))
即,遍历该组的所有物品,找到能带来最大价值的那个物品。 -
初始化
当i=0时,无论背包容量为何值,背包内没有物品,总价值为0。
DP优化
方法原理与01背包相同,不在赘述
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int v[N][N], w[N][N];
int s[N];
int n, m;
int f[N];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
{
scanf("%d", &s[i]);
for(int j = 1; j <= s[i]; j ++)
scanf("%d%d", &v[i][j], &w[i][j]);
}
for(int i = 1; i <= n; i ++)
{
for(int j = m; j >= 0; j --)
{
for(int k = 1; k <= s[i]; k ++)
if(j - v[i][k] >= 0)f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
}
}
printf("%d", f[m]);
return 0;
}
三、完全背包问题
题目描述
有N件物品和一个容量是V的背包。每种物品都有无限件可用。
第i件物品的体积是viv_ivi,价值是wiw_iwi
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wivi,wivi,wi,用空格隔开,分别表示第 i件物品的体积和价值。
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10