Dynamic Programming
记忆化搜索
记忆化搜索是一种通过记录已经遍历过的状态的信息,从而避免对同一状态重复遍历的搜索实现方式。
因为记忆化搜索确保了每个状态只访问一次,它也是一种常见的动态规划实现方式。
背包DP
引入
有
由于每个物体只有两种可能的状态(取与不取),对应二进制中的 0 和 1,这类问题便被称为「0-1 背包问题」。
0-1背包
解释
例题中已知条件有第
设:DP 状态
考虑转移。 假设当前已经处理好了前
当其不放入背包时,背包的剩余容量不变,背包中物品的总价值也不变,故这种情况的最大价值为
; 当其放入背包时,背包的剩余容量会减小
,背包中物品的总价值会增大 ,故这种情况的最大价值为 。
由此可以得出状态转移方程:
由于对
务必牢记并理解这个转移方程,因为大部分背包问题的转移方程都是在此基础上推导出来的
完全背包
解释
完全背包模型与 0-1 背包类似,与 0-1 背包的区别仅在于一个物品可以选取无限次,而非仅能选取一次。
我们可以借鉴 0-1 背包的思路,进行状态定义:设
需要注意的是,虽然定义与 0-1 背包类似,但是其状态转移方程与 0-1 背包并不相同。
过程
可以考虑一个朴素的做法:对于第
状态转移方程如下
考虑做一个简单的优化。可以发现,对于
与 0-1 背包相同,我们可以将第一维去掉来优化空间复杂度。如果理解了 0-1 背包的优化方式,就不难明白压缩后的循环是正向的(也就是上文中提到的错误优化)。
多重背包
多重背包也是 0-1 背包的一个变式。与 0-1 背包的区别在于每种物品有
一个很朴素的想法就是:把「每种物品选
时间复杂度
混合背包
混合背包就是将前面三种的背包问题混合起来,有的只能取一次,有的能取无限次,有的只能取
这种题目看起来很吓人,可是只要领悟了前面几种背包的中心思想,并将其合并在一起就可以了。下面给出伪代码:
二维费用背包
有
现在有
这道题是很明显的 0-1 背包问题,可是不同的是选一个物品会消耗两种价值(经费、时间),只需在状态中增加一维存放第二种价值即可。
这时候就要注意,再开一维存放物品编号就不合适了,因为容易 MLE。
分组背包
有
这种题怎么想呢?其实是从「在所有物品中选择一件」变成了「从当前组中选择一件」,于是就对每一组进行一次 0-1 背包就可以了。
再说一说如何进行存储。我们可以将
有依赖的背包
小明有
目标是让所有购买的物品的
考虑分类讨论。对于一个主件和它的若干附件,有以下几种可能:只买主件,买主件 + 某些附件。因为这几种可能性只能选一种,所以可以将这看成分组背包。
如果是多叉树的集合,则要先算子节点的集合,最后算父节点的集合。
泛化物品背包
这种背包,没有固定的费用和价值,它的价值是随着分配给它的费用而定。在背包容量为
小优化
根据贪心原理,当费用相同时,只需保留价值最高的;当价值一定时,只需保留费用最低的;当有两件物品
背包问题变种
输出方案
输出方案其实就是记录下来背包中的某一个状态是怎么推出来的。我们可以用
求方案树
对于给定的一个背包容量、物品费用、其他关系等的问题,求装到一定容量的方案总数。
这种问题就是把求最大值换成求和即可。
例如 0-1 背包问题的转移方程就变成了:
初始条件:
因为当容量为
求最优方案总数
要求最优方案总数,我们要对 0-1 背包里的
这样修改之后,每一种 DP 状态都可以用一个
转移方程:
如果
如果
如果
初始条件:
因为背包体积最大值有可能装不满,所以最优解不一定是
最后我们通过找到最优解的价值,把
背包的第 k 优解
普通的 0-1 背包是要求最优解,在普通的背包 DP 方法上稍作改动,增加一维用于记录当前状态下的前 k 优解,即可得到求 0-1 背包第
具体来讲:
转移时,普通背包最优解的求法是
整体时间复杂度为