3
0

重读《背包九讲》:0-1 背包问题

2026-09-15
2026-09-15

关于《背包九讲》

本文参考的《背包九讲》文章链接为:V2.pdf

0-1 背包问题

题目:有 N 件物品和一个容量为 V 的背包,第 i 件物品的费用(占用背包空间)为 c_i,价值为 w_i。求解可获得的最大价值。

思路:对于每一个物品,可以选择选或不选。如果不选的话,f_{i,v} 的状态直接从 f_{i-1,v} 继承(相当于不变);如果选的话,f_{i,v} 的状态就从 f_{i-1,v-c_i} 继承,加上选择获得的价值 w_i。所以,我们可以得到如下状态转移方程:

f_{i,v} = \max(f_{i-1,v}, f_{i-1,v-c_i}+w_i)

这是背包问题最基础的一个公式。如果第 i 件物品不选,其实就相当于在前 i-1 件物品中进行选择;如果第 i 件物品选,那么,就是在前 i-1 件物品的基础上,花费多了 c_i,价值多了 w_i。由于背包容量不能是负的,所以在遍历 v 的这一维时,要从 c_i 开始遍历到 V。C++ 代码如下:

for (int i=1; i<=n; i++) {
    for (int j=c[i]; j<=V; j++) {
        f[i][j] = max(f[i-1][j], f[i-1][j-c[i]] + w[i]);
    }
}

空间优化:容易发现,第 i 件物品的数据都是从第 i-1 件物品的数据继承的,与更前面的数据无关。由此,我们可以在每次遍历中摒弃掉前面过期的数据,也就是使用滚动数组优化。则状态转移方程为:

f_v = \max(f_v, f_{v-c_i}+w_i)

注意,由于我们更新要使用前面旧的数据,为了避免前面的数据被提前覆盖,需要倒序遍历。C++ 代码如下:

for (int i=1; i<=n; i++) {
    for (int j=V; j>=c[i]; j--) {
        f[j] = max(f[j], f[j-c[i]] + w[i]);
    }
}

初始化的细节问题:背包问题分为两种问法:一种问法是物品恰好装满背包,一种问法不要求物品恰好装满背包。对于第一种问法,需要将 f_0 初始化为 0,其余全部初始化为 -\infty。对于第二种问法,全部初始化为 0 即可。

这是因为,f 数组中保存的实际上是所有合法状态中的最大值。如果要求恰好装满背包,那么只有背包容量为 0 时什么都不装是合法的,其他容量在什么都不装时没有合法状态,需要初始化为负无穷。如果不要求恰好装满背包,那么对于每个容量,什么都不装都是一种花费和收益都为 0 的合法状态。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或者给予支持!

重读《背包九讲》:0-1 背包问题
/2026/09/chong-du-bei-bao-jiu-jiang-0-1-bei-bao-wen-ti
作者
Tiger
发布于
2026-09-15
许可协议
CC BY-NC-SA 4.0