题目解析
就是01背包,直接来
这也是一道非常经典的动态规划 (DP) 题目,它的学名叫做 “0/1 背包问题” (0/1 Knapsack Problem)。
1. 题目分析
-
问题模型:你有一个容量有限的容器(背包/袋子),面前有一堆物品,每个物品都有自己的体积(消耗的成本)和价值(获得的收益)。
-
规则:对于每一个骨头,你只有两种选择:
-
拿走 (1):占用背包体积,获得价值。
-
不拿 (0):不占用体积,没有价值。
(注:不能把骨头切开拿一半,也不能重复拿同一个骨头,这就是为什么叫 “0/1” 背包)。
-
-
目标:在不超过背包总容量
的前提下,让装入的骨头总价值最大。
2. 核心思路
如果要用暴力枚举(拿或不拿),复杂度是
我们定义一个数组 dp。
状态转移方程
当我们面对第
这个公式的意思是:
(不拿):我不拿这块骨头,那么最大价值就维持之前的状态,即容量为 时的旧值。 (拿):我决定拿这块骨头。前提是我必须给它腾出 的空间。那么我就要找“容量为 时的最大价值”,加上当前这块骨头的价值 。 :在这两种选择中,谁大我就选谁。
3. 关键技巧:一维数组优化
为了节省空间,我们通常使用一维数组。但在使用一维数组解决 0/1 背包问题时,内层循环必须从大到小(倒序)遍历。
-
为什么要倒序?
如果从小到大遍历(例如
从 到 ),当我们计算 时,用到的 可能已经是“放入了当前物品”之后的新值了。这就导致一个物品被放入了多次(变成了“完全背包问题”)。 倒序遍历(
从 到 )能保证我们在计算 时,引用到的 还是上一轮(即没放当前物品前)的状态。
4. 代码实现 (C++)
注意:题目输入中,第二行是价值,第三行是体积。写代码时读取顺序不要搞反。
#include <iostream>
#include <vector>
#include <cstring> // for memset
#include <algorithm> // for max
using namespace std;
// 全局变量定义,N和V最大为1000
const int MAXV = 1005;
int dp[MAXV];
int values[MAXV];
int volumes[MAXV];
void solve() {
int N, V;
cin >> N >> V;
// 清空 DP 数组,因为有多个测试用例
// 将 dp 数组全部初始化为 0
memset(dp, 0, sizeof(dp));
// 读取价值 (题目说第二行是价值)
for (int i = 1; i <= N; ++i) {
cin >> values[i];
}
// 读取体积 (题目说第三行是体积)
for (int i = 1; i <= N; ++i) {
cin >> volumes[i];
}
// 核心算法:0/1 背包
for (int i = 1; i <= N; ++i) { // 遍历每一个骨头
// 必须倒序遍历容量!从 V 到 volumes[i]
for (int j = V; j >= volumes[i]; --j) {
dp[j] = max(dp[j], dp[j - volumes[i]] + values[i]);
}
}
cout << dp[V] << endl;
}
int main() {
// 优化输入输出
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
5. 样例推演 (Dry Run)
输入:
Plaintext
N=5, V=10
价值: 1 2 3 4 5
体积: 5 4 3 2 1
初始状态:dp 数组全是 0。
- 骨头1 (体积5, 价值1):
从 10 到 5, dp[j]更新为。 dp变成:[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1](下标0-10)
- 骨头2 (体积4, 价值2):
: - …
: : - 现在
dp中较大的容量位置已经开始累积价值。
- 骨头3 (体积3, 价值3):
: . 此时 还是 1 (骨头1的贡献)。所以 。 - …这里继续更新。
…快进到最后…
我们可以直观地看:要凑出体积
观察数据:体积越小,价值反而越高(反直觉的骨头)。
- 拿骨头5 (体积1, 价值5)
- 拿骨头4 (体积2, 价值4)
- 拿骨头3 (体积3, 价值3)
- 拿骨头2 (体积4, 价值2)
总体积 =
总价值 =
程序输出 14。
总结
这道题是 背包问题 家族中最基础的成员。
- 要点 1:状态定义
表示容量为 时的最大价值。 - 要点 2:一维数组优化时,内层循环从大到小。
- 要点 3:多组测试数据 (
组),每次记得初始化/清空数组。