把每个物品看成只能选一次的背包物品,按容量做一维 0/1 背包,维护不超过 M 时的最大价值。
OJ: luogu
题目 ID: P2871
难度:普及-
标签:动态规划01背包背包
日期: 2026-06-19 15:32
题意
有 N 件物品和一个容量为 M 的背包。
- 第
i件物品有重量W[i] - 第
i件物品有价值D[i] - 每件物品最多只能选一次
要求在总重量不超过 M 的前提下,让总价值最大。
这张表把题目直接翻译成了背包模型:
| 原题对象 | 背包含义 |
|---|---|
| 一件物品 | 一个只能选一次的物品 |
重量 W[i] |
物品重量 |
价值 D[i] |
物品价值 |
背包容量 M |
背包容量 |
从表里可以看到,本题就是最标准的一维 0/1 背包。
思路
先看最直接的暴力:
cpp
#include <bits/stdc++.h>
using namespace std;
// brute.cpp:小数据暴力解,使用 01 序列枚举每个物品选或不选。
const int MAXN = 3405;
int n, m;
int weight[MAXN];
int value[MAXN];
int choose_item[MAXN]; // choose_item[i] = 0/1,表示第 i 个物品不选/选
int answer;
bool check() {
int used_weight = 0;
for (int i = 1; i <= n; i++) {
if (choose_item[i] == 1) used_weight += weight[i];
}
return used_weight <= m;
}
int calc_answer() {
int total_value = 0;
for (int i = 1; i <= n; i++) {
if (choose_item[i] == 1) total_value += value[i];
}
return total_value;
}
// dfs_choose 只负责生成完整 01 序列,合法性和答案统计放到叶子节点。
void dfs_choose(int dep) {
if (dep == n + 1) {
if (check()) {
int current_value = calc_answer();
if (answer < current_value) answer = current_value;
}
return;
}
for (int i = 0; i <= 1; i++) {
choose_item[dep] = i;
dfs_choose(dep + 1);
}
}
void read_input() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> weight[i] >> value[i];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
read_input();
dfs_choose(1);
cout << answer << '\n';
return 0;
}brute.cpp 把每个物品看成一个 01 选择:choose_item[i] = 0/1 表示不选或选。递归先生成完整选择,叶子节点再检查总重量是否超过 M,并统计奖励。
这个做法显然正确,但复杂度是
关键观察是:每个物品只有“选 / 不选”两种决策,和 0/1 背包完全一致。
所以设:
dp[j]表示容量不超过j时能获得的最大奖励
这张表说明状态定义:
| 状态 | 含义 |
|---|---|
dp[j] |
容量不超过 j 时能获得的最大奖励 |
处理第 i 件物品 (W[i], D[i]) 时:
- 不选它:
dp[j]保持原值 - 选它:从
dp[j - W[i]]转移过来,再加上D[i]
于是转移就是:
dp[j] = max(dp[j], dp[j - W[i]] + D[i])
因为每件物品只能选一次,所以容量必须倒序枚举。
最后输出 dp[M] 即可。
DP 公式
设
其中
公式解释:这是最标准的 0/1 背包。dp_j 表示容量 j 内的最大价值,选当前物品时消耗重量并增加奖励。
代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 3405;
const int MAXM = 12885;
int n, m;
int weight[MAXN];
int value[MAXN];
int dp[MAXM]; // dp[j] 表示容量不超过 j 时能获得的最大奖励
void read_input() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> weight[i] >> value[i];
}
}
void solve() {
for (int i = 1; i <= n; i++) {
// 每个物品只能选一次,所以容量必须倒序枚举。
for (int j = m; j >= weight[i]; j--) {
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
cout << dp[m] << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
read_input();
solve();
return 0;
}复杂度
- 时间复杂度:
- 空间复杂度:
总结
这题是 0/1 背包最标准的模板题之一:
- 每个物品最多选一次
- 只有一个容量限制
- 目标是最大化总价值
以后看到这类题,就可以直接往一维 0/1 背包上想。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。
