01背包 无非是物品是拿还是不拿的问题
例
有四件物品,背包的总容量是8,如何装可以使背包中的物品总价值最大
物品编号 1 2 3 4
物品重量 2 3 4 5
物品价值 3 4 5 8
将该题抽象为函数f(x, y),表示当前的价值,则本题为 f(k, w) k为物品数,w为背包的总容量,故本题实际上是求解 f(4, 8) 的最大值
对任意一件物品,可拿,可不拿,则对该物品进行分类讨论,这里对第四件物品进行分析
拿第四件物品,问题转换为,求解 f(4 - 1, 8 - 5) + 8,即 f(3, 3),问题转变为,求解背包容量为3时,从前三件物品中选出的最大价值
不拿第四件物品,问题转换为,求解 f(4 - 1, 8),即 f(3, 8),问题转变为,求解背包容量为8时,从前三件物品中选出的最大价值
再对上述问题进行递归讨论,可以得到下面这一棵递归树(√ 表示选该物品,× 表示不选该物品)

因此,本题的状态转移方程为 f(k, w) = max(f(k - 1, w - wk) + vk, f(k - 1, w));
其中 wk表示当前物品的重量,vk表示当前物品的价值
对上述过程建表,横坐标表示当前背包剩余的容量,纵坐标表示当前正在选择的物品

对f(1, 2)进行讨论,若拿,f(1, 2) 转化为 f(0, 0) + 3,若不拿,f(1, 2) 转化为 f(0, 2)
选两个里面的最大值,为3,继续按照这个步骤填表

对f(2, 5)进行讨论,若拿,f(2, 5) 转化为 f(1, 2) + 4,若不拿,f(2, 5) 转化为 f(1, 5)
根据表可查,f(1, 2) = 3,f(1, 5) = 3,因此,f(2, 5) = max(3 + 4, 3) = max(7, 3) = 7
将上述过程改写为伪代码形式
设物品重量数组为w[i],价格数组为v[i]
for (int i = 1; i <= 待选物品下标; i++) {
for (int j = 1; j <= 当前背包的剩余容量; j++) {
// j - w[i] 为背包剩余重量减物品重量,大于等于0表示背包是否可以装得下,装得下才考虑要不要装这件物品,否则直接跳过该物品(相当于不装这件物品,f(i, j) = f(i - 1, j))
f(i, j) = j - w[i] >= 0 ? max(f(i - 1, j - w[i]) + v[i], f(i - 1, j)) : f(i - 1, j);
}
}
可以看到,对于第i行,递推公式f(i, j)只跟 f(i - 1, j) 和 f(i - 1, j - w[i]) + v[i] 的i - 1行数据有关,与当前行无关。因此,对于当前的第i行,j从前往后遍历,或从后往前遍历,效果完全相同。
下面给出对n件物品可选的从后往前遍历的代码(仅仅是将上面从前往后的遍历方向反转)
for (int i = 1; i <= n; i++) {
for (int j = 当前背包的剩余容量; j > 0; j--) {
f(i, j) = j - w[i] >= 0 ? max(f(i - 1, j - w[i]) + v[i], f(i - 1, j)) : f(i - 1, j);
}
}