题目
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0 < N, V ≤ 10000 < N, V ≤ 1000
0 < vi, wi ≤ 10000 < vi, wi ≤ 1000
输入样例
输出样例
朴素写法
分析
状态转移方程
- 不装第 i 件物品:
dp[i][j] = dp[i - 1][j]
- 装 k 个第 i 件物品:
dp[i][j] = dp[i - 1][j - K * v[i]] + k * w[i]
合起来就是dp[i][j] = dp[i - 1][j - K * v[i]] + k * w[i] (0 <= k * v[i] <= j)
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include<bits/stdc++.h> using namespace std;
const int N = 1010; int v[N], w[N]; int dp[N][N];
int main() { int n, m; scanf("%d%d", &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++) for(int k = 0; k * v[i] <= j; k++) dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + w[i] * k); printf("%d", dp[n][m]); return 0; }
|
初步优化
思路
首先我们来看一下dp[i][j] = dp[i - 1][j - k * v[i]] + k * w[i]
这个式子(用 v代替 v[i],w代替 w[i])
1 2 3 4 5 6
| dp[i][j] = max(dp[i-1][j], dp[i-1][j-v]+w, dp[i-1][j-2v]+2w,...) dp[i][j-v] = max( dp[i-1][j-v], dp[i-1][j-2v]+w,...) 根据上面两个式子,可以得到关系: dp[i][j] = dp[i][j - v] + w 即dp[i][j]可以用这一层的上一个状态再装一件 i 得到 由此可得状态转移方程:dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i])
|
举例
用一个具体例子来辅助理解(题目数据):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| v[1] = 1, w[1] = 2 v[2] = 2, w[2] = 4 v[3] = 3, w[3] = 4 v[4] = 4, w[4] = 5 dp[1][1] = max(dp[0][1] == 0, dp[1][0] + w[1] == 2) == 2 dp[1][2] = max(dp[0][2] == 0, dp[1][1] + w[1] == 4) == 4 dp[1][3] = max(dp[0][3] == 0, dp[1][2] + w[1] == 6) == 6 dp[1][4] = max(dp[0][4] == 0, dp[1][3] + w[1] == 8) == 8 dp[1][5] = max(dp[0][5] == 0, dp[1][4] + w[1] == 10) == 10
dp[2][2] = max(dp[1][2] == 4, dp[2][0] + w[2] == 4) == 4 dp[2][3] = max(dp[1][3] == 6, dp[2][1] + w[2] == 6) == 6 dp[2][4] = max(dp[1][4] == 8, dp[2][2] + w[2] == 8) == 8 dp[2][5] = max(dp[1][5] == 10, dp[2][3] + w[2] == 10) == 10
dp[3][3] = max(dp[2][3] == 6, dp[3][0] + w[3] == 4) == 6 dp[3][4] = max(dp[2][4] == 8, dp[3][1] + w[3] == 6) == 8 dp[3][5] = max(dp[2][5] == 10, dp[3][2] + w[3] == 8) == 10
dp[4][4] = max(dp[3][4] == 8, dp[4][0] + w[4] == 5) == 8 dp[4][5] = max(dp[3][5] == 10, dp[4][1] + w[4] == 7) == 10
|
初步优化结果
有了上面的方程,那么 k 循环就可以不要了
1 2 3 4 5
| for(int i = 1; i <= n; i++) for(int j = 0; j <= m; j++) { dp[i][j] = dp[i - 1][j]; if(v[i] <= j) dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]); }
|
与01背包比较
这个和 01 背包像,我们来比较一下:
1 2
| 01背包: 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][j - v[i]] + w[i])
|
再次优化
因为和01背包很像,所以我们很容易想到进一步的优化:
1 2 3
| for(int i = 1; i <= n; i++) for(int j = v[i]; j <= m; j++) dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
|
为什么不需要倒叙呢?
我们上一篇讲到01背包的状态转移方程需要的是上一轮的状态值,所以不能先更新状态值
而完全背包需要的是本轮中上一步的状态值,所以需要从小到大遍历,先更新状态值
最终代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include<bits/stdc++.h> using namespace std;
const int N = 1010; int v[N], w[N]; int dp[N];
int main() { int n, m; scanf("%d%d", &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 = v[i]; j <= m; j++) dp[j] = max(dp[j], dp[j - v[i]] + w[i]); printf("%d", dp[m]); return 0; }
|