题解:洛谷 P2639 Bessie's Weight Problem
【题目来源】
洛谷:P2639 [USACO09OCT] Bessie's Weight Problem G - 洛谷
【题目描述】
Bessie 像她的诸多姊妹一样,因为从 Farmer John 的草地吃了太多美味的草而长出了太多的赘肉。所以 FJ 将她置于一个及其严格的节食计划之中。她每天不能吃多过 \(H (5 \le H \le 45,000)\) 公斤的干草。Bessie 只能吃一整捆干草;当她开始吃一捆干草的之后就再也停不下来了。她有一个完整的\(N (1 \le N \le 500)\) 捆可以给她当作晚餐的干草的清单。她自然想要尽量吃到更多的干草。很自然地,每捆干草只能被吃一次(即使在列表中相同的重量可能出现 \(2\) 次,但是这表示的是两捆干草,其中每捆干草最多只能被吃掉一次)。给定一个列表表示每捆干草的重量 \(S_i (1 \le S_i \le H)\) ,求 Bessie 不超过节食的限制的前提下可以吃掉多少干草(注意一旦她开始吃一捆干草就会把那一捆干草全部吃完)。
【输入】
第一行有两个由空格隔开的整数 \(H\) 和 \(N\)。
第 \(2\) 到第 \(N+1\) 行,第 \(i+1\) 行是一个单独的整数,表示第 \(i\) 捆干草的重量 \(S_i\)。
【输出】
第一行一个单独的整数表示 Bessie 在限制范围内最多可以吃多少公斤的干草。
【输入样例】
56 4
15
19
20
21
【输出样例】
56
【核心思想】
-
问题分析:给定 \(N\) 捆干草,每捆重量 \(S_i\),Bessie 每天最多吃 \(H\) 公斤。每捆只能完整吃或不吃,求不超过 \(H\) 的前提下能吃的最大重量。这是一个经典的01背包问题,每捆干草是"物品",重量既是体积也是价值,背包容量为 \(H\)。
-
算法选择:
- 01背包(二维):\(dp[i][j]\) 表示前 \(i\) 捆干草、总重量不超过 \(j\) 时的最大可吃重量
- 01背包(一维滚动数组优化):\(dp[j]\) 表示背包容量为 \(j\) 时的最大价值,逆序遍历容量避免重复选取
-
关键步骤:
- 读取输入:\(H\)(容量)和 \(N\)(物品数),以及 \(N\) 个重量 \(S_i\)
- 初始化:\(dp\) 数组全 \(0\)
- 01背包递推(\(i\) 从 \(1\) 到 \(N\)):
- 二维版:对于 \(j\) 从 \(1\) 到 \(H\):
- 若 \(j < S_i\):\(dp[i][j] = dp[i-1][j]\)(装不下)
- 否则:\(dp[i][j] = \max(dp[i-1][j],\ dp[i-1][j-S_i] + S_i)\)(不选或选)
- 一维滚动优化:对于 \(j\) 从 \(H\) 到 \(S_i\) 逆序:
- \(dp[j] = \max(dp[j],\ dp[j-S_i] + S_i)\)
- 二维版:对于 \(j\) 从 \(1\) 到 \(H\):
- 输出 \(dp[H]\)(或 \(dp[N][H]\))
-
时间/空间复杂度:
- 时间复杂度:\(O(N \cdot H)\),\(N \le 500\),\(H \le 45000\)
- 空间复杂度:\(O(H)\),一维滚动数组优化后
-
01背包的核心思想:
- 选或不选的决策:对于每捆干草,只有两种状态——吃(占用 \(S_i\) 容量,获得 \(S_i\) 价值)或不吃(不占用容量,价值不变)
- 最优子结构:\(dp[j]\) 仅依赖 \(dp[j]\) 和 \(dp[j-S_i]\),满足无后效性
- 滚动数组优化:逆序遍历容量保证每个物品只被选取一次,将空间从 \(O(N \cdot H)\) 降为 \(O(H)\)
- 价值=重量:本题中每捆干草的价值等于其重量,是01背包的特例,但递推公式不变
- 适用于"有限物品 + 容量约束 + 最大化价值"类经典背包问题
【解题思路】

【算法标签】
普及- #01背包
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int h, n, wi[505],ci[505];
int dp[205][45005] = {0};
int main()
{
cin >> h >> n; // 输入h和n,类似01背包中的N和M(h=M,n=N)
for (int i=1; i<=n; i++) { // 记录n件物品的Wi和Ci
cin >> wi[i];
ci[i] = wi[i]; // 题目描述中没有ci,但最后求最大重量,可以理解为最大价值,就让ci=wi
}
for (int i=1; i<=n; i++) { // 遍历n个物品
for (int j=1; j<=h; j++) { // 遍历背包容量,即吃干草重量
if (j<wi[i]) { // 如果背包装不下
dp[i][j] = dp[i-1][j]; // 则此i件物品不装的价值等于i-1时背包容量为j的价值
} else { // 如果装的下
dp[i][j] = max(dp[i-1][j], dp[i-1][j-wi[i]] + ci[i]); // 计算i-1时背包容量为j为价值,与装入该物品的价值的最大值,dp[i-1][j-wi[i]]表示没有装入当前物品的价值,+ci[i]表示增加当前物品后的价值
}
}
}
cout << dp[n][h] << endl; // 输出结果
return 0;
}
// 01背包完全体
#include <bits/stdc++.h>
using namespace std;
int h, n, wi[505],ci[505];
// int dp[205][45005] = {0};
int dp[45005] = {0};
int main()
{
cin >> h >> n; // 输入h和n,类似01背包中的N和M(h=M,n=N)
for (int i=1; i<=n; i++) { // 记录n件物品的Wi和Ci
cin >> wi[i];
ci[i] = wi[i]; // 题目描述中没有ci,但最后求最大重量,可以理解为最大价值,就让ci=wi
}
for (int i=1; i<=n; i++) { // 遍历n个物品
for (int j=h; j>=1; j--) { // 遍历背包容量,即吃干草重量(此处是滚动数组模板)
if (j>=wi[i]) { // 如果装的下
dp[j] = max(dp[j], dp[j-wi[i]] + ci[i]); // dp[j-wi[i]]表示没有装入当前物品的价值,+ci[i]表示增加当前物品后的价值
}
}
}
cout << dp[h] << endl; // 输出背包容量为h时是的结果
return 0;
}
【运行结果】
56 4
15
19
20
21
56
// dp[n][h]打印结果:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15
0 0 0 0 0 0 0 0 0 0 0 0 0 0 15 15 15 15 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34
0 0 0 0 0 0 0 0 0 0 0 0 0 0 15 15 15 15 19 20 20 20 20 20 20 20 20 20 20 20 20 20 20 34 35 35 35 35 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 54 54 54
0 0 0 0 0 0 0 0 0 0 0 0 0 0 15 15 15 15 19 20 21 21 21 21 21 21 21 21 21 21 21 21 21 34 35 36 36 36 39 40 41 41 41 41 41 41 41 41 41 41 41 41 41 54 55 56
浙公网安备 33010602011771号