#dp #背包dp #多重背包 #算法优化 #经验总结
看到多重背包 -> 01 背包的转化操作中,使用了二进制拆分。第一眼觉得这种方法的正确性很难判断,就想了个把小时,总算是想明白了,写篇文纪念一下。
为了防止误会,在原问题中的物品不加引号,在转换后的问题中的物品加引号
多重背包问题背景:存在容量为 的背包和 组物品,第 组物品有 个且每个价值都为 ,求背包能装下的物品价值和的最大值。
TODO: 使用二维 dp 写一下思路
为了更好地表达,下面使用
"物品"的数量
来表示拆分后的"物品"于原问题中的等效数量
我们可以发现多重背包问题并没有这么多限制,即可以尝试把问题转化为 01 背包问题。
最简单最朴素的方法就是把"组"的概念拆掉:把「每种物品选 次」等价转换为「有 个相同的物品,每个物品选一次」1。
那这样的话,可见的是复杂度爆炸(时间复杂度为 ),且显然存在子问题重复(多个"物品"的价值相同)。
显然,复杂度中的 部分无法再优化了,我们只能从 处入手。为了表述方便,我们用 代表第 种物品拆分出的第 个物品。2
在朴素的做法中,, 均表示相同物品。而效率低的原因在于我们在后续跑 01 背包时进行了大量重复性的工作。
举个栗子,这个算法实际上考虑了「同时选 」与「同时选 」这两个完全等效的情况。那么优化拆分方式就成为了解决问题的突破口。3
TODO: 使用 01 背包写一下朴素优化的代码
观察上面的操作,其正确性是显而易见的:上述的拆分操作不会影响到答案。即原先需要选择某个组中的 个物品,在拆分后也会进行这样的选择(选择等效的 个"物品")。
那么什么情况下会没有正确性呢?
举个栗子,假设我们使用了某些奇怪的拆分操作,把某个有 个物品(在朴素的做法中,这个组会被划分成 )的组划分成了 个物品的形式,而正解会选择这个组的 个物品。显然,这种划分方式无论如何也没办法凑出来 这个数字,而朴素的做法可以()。
根据上面的例子,可以发现错误的划分方法会增加限制(比如对于某个组,拆分后的"物品"凑不出来正解中选择的物品数量),从而得不到正解。
在这里可以看出一些端倪:如果我们对任意组(假设有 个物品)进行拆分,得到的"物品"们的数量可以凑出来任意 ,那是不是不会增加限制呢?
这里就可以尝试用 CS 中常用的二进制拆分操作,即对于某组中的 个物品,我们拆成下面的"物品"集合(下面是不太严谨的表述,意会即可)
注意,上面提到的集合 是我们构造的有特殊性质的集合。
为什么要这么构造呢?我们可以简单看一下构造,可以简单地分为两部分:
可以发现,只有 项是比较特殊的,为了方便,在后面称呼 ta 为 。
因为这个值的意义是某个"物品"的数量,后面会使用 "物品"来代指"物品"的数量为 的"物品"。
接下来就应该尝试证明这种拆分能表示的答案区间了。在这里进行分类讨论,分别是 一定不选 "物品"的情况 和 一定选 "物品" 的情况。
可以发现,一定不选 "物品"时,数量和最大为 。(提一嘴没啥用的,这能很容易地证明这个集合的数量和为 。)
在理解下面这一句的时候,请使用组合排列与代数的方法思考!
在一定不选 "物品"时,剩下的"物品"的数量的子集的和 。
在一定选 "物品"时,其子集的和 。
在这里,因为 ,我们可以证明
所以,在这里 和 的并集可以覆盖整个区间,即
而在 01 背包问题中,子问题是可重复贡献问题,所以这两个子问题完全可以合并,得到完整的答案区间(和倍增 ST 表的子问题合并的可行性证明比较接近)。
总结一下,使用这种方法,我们可以做到:
TODO: 写一下代码
此句述来自 OI-Wiki (OI-wiki/docs/dp/knapsack.md at master · OI-wiki/OI-wiki (github.com)) ↩
此段描述改写自 OI-Wiki ↩
此段描述改写自 OI-Wiki ↩
本文作者:CornWorld
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!