题目
有
N
N
N种物品和一个容量是
V
V
V的背包,每种物品都有无限件可用。
第
i
i
i 种物品的体积是
v
i
v_i
vi,价值是
w
i
w_i
wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
输入格式
第一行两个整数,
N
N
N,
V
V
V,用空格隔开,分别表示物品种数和背包容积。
接下来有
N
N
N 行,每行两个整数
v
i
v_i
vi,
w
i
w_i
wi,用空格隔开,分别表示第
i
i
i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
分析
状态表示
用一个数组f[i][j]
来表示只看前i
个物品,在总体积是j
的情况下,总价值最大是多少。
那么,题目要求在背包容量为V
的条件下,从N
件物品中选取物品使得的总价值最大,状态表示应为f[N][V]
.
状态计算
在进行状态计算之前,先得对状态做一个划分。对于01背包,f[i][j]的状态只有两种:
不选第i个物品 | 选第i个物品 |
---|---|
f[i-1][j] | f[i-1][j-v[i]] + w[i] |
取两种状态的最大值
对于完全背包,物品可以取无限个,不过k最多到k*w=v时为止,因为不能超背包容量
那么状态可以划分为:
不选第i个物品(选0个) | 选1个第i个物品 | 选2个第i个物品 | … | 选k个第i个物品 |
---|---|---|---|---|
f[i-1][j] | f[i-1][j-v[i]] + w[i] | f[i-1][j-2*v[i]] + 2*w[i] | … | f[i-1][j-k*v[i]] + k*w[i] |
找到这些状态的最大值即可。
因此,我们只需要在0-1背包的基础上,多循环一次k
#include<iostream>
using namespace std;
const int N = 1010;
int n, m;
int f[N][N], v[N], w[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 = 0; j <= m; j ++ )
for(int k = 0; k * v[i] <= j; k ++ )
f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
cout << f[n][m] << endl;
}
优化
上面的算法时间复杂度过高。
回顾下状态切分 对于f[i][j]
来说
不选第i个物品 | 选1个第i个物品 | 选2个第i个物品 | 选3个第i个物品 |
---|---|---|---|
f[i-1][j] | f[i-1][j-v[i]] + w[i] | f[i-1][j-2*v[i]] + 2*w[i] | f[i-1][j-3*v[i]] + 3*w[i] |
而巧就巧在f[i][j-v[i]]
的状态划分为
不选第i个物品 | 选1个第i个物品 | 选2个第i个物品 |
---|---|---|
f[i-1][j-v[i]] | f[i-1][j-2*v[i]] + w[i] | f[i-1][j-3*v[i]] + 2*w[i] |
我们发现,f[i][j]
中选择1–k个第i个物品和f[i][j-v[i]]
中选择k–1个第i个物品正好是对应的(只相差w[i])
因此,对于f[i][j]
,选择k个第i个物品的最大值为f[i][j-v[i]] + w[i]
,我们不再需要循环k个物品了,状态划分简化为:
不选第i个物品 | 选择k个第i个物品 |
---|---|
f[i-1][j] | f[i][j-v[i]] + w[i] |
取二者的最大值即可。
代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1002;
int n, m;
int f[N][N];
int v[N], w[N];
int main(){
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 = 0; j <= m; j++) {
f[i][j] = f[i-1][j];
if (j >= v[i]) {
f[i][j] = max(f[i][j], f[i][j-v[i]] + w[i]);
}
}
}
cout << f[n][m] << endl;
return 0;
}
可以用滚动数组,再次优化。