1. 问题描述
1.1 题目:
有 N 件物品和一个最多能背重量为 V 的背包。第i件物品的重体积是v[i],得到的价值是w[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。输出最大价值。
1.2 输出格式
输出一个整数,表示最大价值。
1.3 时/空限制
1s / 64MB
1.4 数据范围
0<N, V≤1000
0<vi, wi≤1000
2. 示例
输入:
4 5
1 2
2 4
3 4
4 5
输出: 8
3. 题解:动态规划
3.1 二维数组
遍历每个物品以及每个背包容量值的最大价值,输出最大价值即可。
i 表示当前遍历第 i 个物品, j表示当前背包最大容量,故当前背包最大值F[i][j]有两种情况
- 若当前物品体积wi < j, 表示当前最大容量都不够存放,故当前最大价值等于遍历前一个物品最大容量为 j 时的价值,即F[i][j] = F[i - 1][j];
- 若当前物品体积wi >= j, 表示当前最大容量可以存放,那么此时有两种情况即放或者不放,选择价值较大的即可。不放时价值同1,存放时F[i][j] = F[i - 1][j - v[i]] + w[i];故F[i][j] = max(F[i - 1][j], F[i - 1][j - v[i]] + w[i])
3.1.1 初始化条件
由于递推公式 F[i][j] = max(F[i - 1][j], F[i - 1][j - v[i]] + w[i]) 出现了 i - 1,故需考虑初始化值
- 若F[0][j]代表第0个物品,则直接初始化为0即可;
- 若F[0][j]代表第1个物品,则需对其进行初始化,当j小于v0式值初始化为0,其余初始化为v0
综合考虑1比较合算;
3.1.2 复杂度估计
- 两层遍历,时间复杂度O(N²)
1001 * 1001 = 10^6, 1s计算量约10^7 ~ 10^8; - 二维数组,空间复杂度O(N²)
1001 * 1001 * sizeof(int) = 4 * 10^ 6 B ≈ 4M < 64 M;
3.1.3 完整代码
# include <iostream>
using namespace std;
int F[1001][1001];
int main()
{
int N, V;
int vol[1001], wor[1001];
cin >> N >> V;
for(int i = 1; i <= N; i++)
cin >> vol[i] >> wor[i];
for(int i = 1; i <= N; i++)
for (int j = 1; j <= V; j++)
{
if (j < vol[i])
F[i][j] = F[i - 1][j];
else
F[i][j] = max(F[i - 1][j], F[i - 1][j - vol[i]] + wor[i]);
}
cout << F[N][V];
return 0;
}
3.2 二维数组
分析可知,每一次的状态仅与上一个 i 相关, 即F[i][j] 只与 f[i - 1]这一行有关系, 故只需要开辟一行数组即可, F[j]用来存放不同i时的最大值。
第 i 个物体的价值有两种情况:
如果此时背包容量不足(j > vi),F[j] = F[j];
否则, F[j] = max(F[j], F[j - v[i])
由于递推公式与 j 前的状态有关,若正序遍历会导致F[j - v[i]被更新并不是上一个 物品对应的最大价值, 故需要倒叙遍历
3.2.1 复杂度估计
- 两层遍历,时间复杂度O(N²)
1001 * 1001 = 10^6, 1s计算量约10^7 ~ 10^8; - 一维数组,空间复杂度O(N)
1001 * sizeof(int) = 4 * 10^ 6 B ≈ 4KB < 64 M;
3.2.2 完整代码
# include <iostream>
using namespace std;
int f[1001];
int main()
{
int N, V;
int vol[1001], wor[1001];
cin >> N >> V;
for(int i = 1; i <= N; i++)
cin >> vol[i] >> wor[i];
for (int i = 1; i <= N; i++)
for(int j = V; j >= vol[i]; j--)
f[j] = max(f[j], f[j - vol[i]] + wor[i]);
cout << f[V] << endl;
return 0;
}