疯狂的背包问题(1) - 01背包问题
使用01背包DP,dp[c]表示容量c时的最大总价值,容量倒序枚举确保每件物品只选一次;同模型的 Python 写法因 3 MB 内存限制必然 MLE。
OJ: luogu
题目 ID: U661986
难度:入门
标签:动态规划01背包背包
日期: 2026-08-08 23:11
题意
N 件物品,每件只能选一次。第 i 件体积 v_i,价值 w_i。背包容量 V。挑选一些物品使总体积不超过 V 且总价值最大,输出最大价值。
思路
一句话本质:每件物品只能选或不选,容量有限制,问最大总价值。
为什么不能直接枚举所有选法?
brute.py 用位掩码枚举了 2^N 种选法,每次检查总体积并更新最优价值。N 最大到 1000,2^1000 是天文数字,根本无法跑完。
既然不能全枚举,我们如何逐步处理这些物品?
可以把物品一个一个地拿出来决策。每处理一件物品,我们只面临两个选择:选它或不选它。
选或不选这个物品,影响了什么?
选了它就占了容量、增加了价值;不选就什么也不变。也就是说,当我们处理到第 i 个物品时,唯一需要关心的状态是"目前已经占用了多少容量"。
如果有多种方式占用相同的容量,我们只需要保留哪一种?
只保留总价值最大的那种。因为后续物品只关心剩余容量还有多少,不关心前面具体选了哪些物品。不同路径到达相同容量,价值更大的那条路径永远更优。这正是 DP 的核心——最优子结构。
状态定义和转移是什么?
定义 dp[c] 表示占用容量 c 时能获得的最大价值。
初始 dp[0] = 0,其他为 0(一件都不选时价值为 0)。
对于每件物品 (v, w),考虑容量 c 从 V 到 v 倒序:
dp[c] = max(dp[c], dp[c - v] + w)含义:要么不选(保持 dp[c]),要么选(从 c-v 转移来,加上价值 w)。
为什么容量必须倒序枚举?
因为每件物品只能选一次。如果正序枚举,处理小的 c 时更新了 dp[c],后面更大的 c’ = c + v 再次用到 dp[c] 时会包含这件物品的影响,等于同一件物品被选了两次。倒序能保证 dp[c - v] 是"没选过当前物品"时的状态。
代码
C++17 正解
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-08-08 23:11
* update_at: 2026-08-08 23:11
*/
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false); cin.tie(0);
int N, V;
cin >> N >> V;
vector<int> dp(V + 1);
for (int i = 0; i < N; ++i) {
int v, w;
cin >> v >> w;
for (int c = V; c >= v; --c)
dp[c] = max(dp[c], dp[c - v] + w);
}
cout << dp[V] << '\n';
return 0;
}Python 版本(会超内存)
import sys
def ints():
for x in sys.stdin.buffer.read().split():
yield int(x)
it = ints()
n = next(it)
C = next(it)
f = [0] * (C + 1)
for _ in range(n):
vol = next(it)
val = next(it)
if vol > C:
continue
for c in range(C, vol - 1, -1):
f[c] = f[c] if f[c] >= f[c - vol] + val else f[c - vol] + val
print(f[C])main.py 的状态定义和倒序转移与 C++ 完全一致,样例输出 8,随机对拍 300 组也与 brute.py 一致,本机最大数据(0.1s。但本题内存限制只有 3 MB(3072 KB),而 CPython 解释器本身的常驻内存就接近 10 MB,实测这份代码的峰值 RSS 约 9948 KB——还没算上 f 数组就已经超出限制。因此它在洛谷上必然 MLE,这里只作为 Python 写法对照,正式提交请使用 main.cpp。
复杂度
- 时间:O(N × V),每件物品遍历 V 个容量
- 空间:O(V),dp 数组大小 V+1
main.py的时间与空间复杂度相同,但解释器运行时开销约10 MB,超过本题3 MB的内存限制,无法通过。
总结
01 背包的核心是每件物品只能选一次,通过容量倒序枚举来保证。dp[c] 记录容量 c 下的最优价值,转移时取"不选"和"选"的最大值。