倒序遍历是为了避免重复选取同一物品,确保0-1背包的“每物至多一次”约束;正向遍历会误成完全背包。

0-1 背包用 std::vector 一维数组滚动更新时,为什么循环要倒着写?
因为正向遍历会重复使用当前轮次已更新的状态,把 0-1 背包变成完全背包——你本想每个物品只选一次,结果它被反复塞进去了。
倒序遍历 dp[j](从 W 到 weight[i])能确保每次更新只依赖上一轮(即未被本轮修改过的)状态。
- 正向:更新
dp[5]后,dp[10]可能又用到这个新dp[5],相当于多拿了一次第i个物品 - 倒序:所有
j对应的dp[j - weight[i]]都还是上一轮值,严格保证“每物至多一次” - 边界注意:内层循环下界是
weight[i],不是0;小于它的容量根本装不下当前物品
std::vector<int></int> 初始化大小和初值搞错会导致什么?
背包问题里最常写成 std::vector<int> dp(W + 1, 0)</int>,但如果你写成 dp(W, 0) 或漏掉 +1,就会访问越界或漏掉容量为 W 的情况。
错误示例:dp[W] 是合法下标,所以数组长度必须是 W + 1。否则 dp[W] 访问的是未分配内存,运行时可能崩溃或返回随机值。
立即学习“C++免费学习笔记(深入)”;
- 初始化为
-1表示“不可达”时,记得全填满:std::vector<int> dp(W + 1, -1)</int> - 若用
INT_MIN做无效标记,别忘了#include <climits></climits>,且加法前检查是否仍为INT_MIN,避免溢出 - 不要用
dp.resize(W + 1)后再手动赋值——容易漏初始化,直接构造更稳
物品体积为 0 怎么办?
标准 0-1 背包假设所有 weight[i] > 0。一旦出现 weight[i] == 0,倒序循环就失效:j - 0 == j,你总在用刚算出来的 dp[j] 更新自己,导致无限叠加。
实际中这类物品要么全拿(如果价值 > 0),要么全不拿(如果价值 ≤ 0)。得单独预处理。
- 先扫一遍,统计所有
weight[i] == 0 && value[i] > 0的价值和,加到最终答案里 - 把这类物品从输入中剔除,再跑常规 DP
- 别想着在 DP 循环里特判
weight[i] == 0——逻辑混杂,易错且难调试
空间优化后还能还原具体选了哪些物品吗?
不能直接还原。一维 dp 数组只存最大价值,丢掉了决策路径信息。
如果题目要求输出方案(比如打印选了哪几个物品),必须保留二维状态,或者额外维护一个 choice 数组记录每步决策。
- 简单做法:用
std::vector<:vector>> choice(n + 1, std::vector<bool>(W + 1))</bool></:vector>,在更新dp[i][j]时同步记下是否选了第i个 - 省空间做法:只存最后一行
choice[j],回溯时从dp[W]往前推,但需保存每轮的weight和value,且无法支持多解任意输出 - 真正卡内存时,宁愿用文件或调试输出临时查中间态,也别为了省几十字节让方案还原逻辑变得脆弱
DP 状态压缩很优雅,但“知道最优值”和“知道怎么达到最优”是两件事。后者永远比前者重得多。










