子集和加总问题(从洛谷博客同步)
给出 ,找出一个子集和为 。
这是 NPC 的,当 的时候可以 背包,当然地可以使用 bitset 压位至 。
值域还是太难受了,考虑怎么压下来值域,因为和为 ,值域又是 ,通过调整顺序总是存在一种方案使得值域在 。但是我们不知道这个顺序,但感觉存太大值域又没用。
所以随机一个顺序,可以证明这个时候值域期望是不大于 的,复杂度可以做到 。
还能再给力一点吗?
时间复杂度 ,空间线性的 01 背包可行性及方案构造(Pisinger, 1999)
因为在很多情况下值域开大到 是很浪费的,中途偏差其实和 差距不会太大——也总是存在一种顺序使得中途偏差不超过 ,前面考虑找到这样的顺序,但是发现不太现实,所以现在来考虑另一种:边加边删。
我们先随便找到一个和与 差不超过 的集合,然后接下来 加入其它数或者删除已经加入的数,那么这样我们考虑的值域就是 的了。
但是删除确实是一件很困难的事情,并不是所有数都能删,我们不能删除之前没加进来的数,我们也不能记录目前集合。
但是如果只在 dp 里面放一个可行性,那属实是有点浪费我们这么小的值域,而且如果要把可删除的信息放在状态里,也没法压缩复杂度。
下面是 Pisinger 最天才的想法:
于是我们来考虑记录可以删除的前缀。
表示前 个数,和 的最大可删除前缀(保证是满的)的长度!
(若和无法为 则设为 )
设初始集合为 。
初始值为
。
考虑转移:
加入一个数
这很好理解——加入一个数就是普通的背包,求 形式。
循环顺序为从大到小做(删了这个数还可能删下一个数)。
虽然有些抽象,但是还是比较好理解:枚举一个可以删去的数字——然后删掉,转移。
但是这样还是 的啊——确实,因为要删很多不同的数。
所以我们给 设置下界 。
因为之前能删除的已经考虑过了——删除某些数后转移到了另一个值,所以我们只用考虑之前没考虑过的值即可了!因为 值对于每一个固定的和都是不减的,所以复杂度为 !
删前缀为什么对?
考虑最终状态下的后半部分选入的集合一定被 dp 决策考虑了,而前面每步一定删除到了能删除的最大位置(这个前缀一定能被拆成若干段删除或不删除的区间),如果存在合法方案,一定会被这个过程包含在内。
想看正确性严谨的证明可以去翻论文。
代码并不复杂,即使需要构造方案也可以在 20 行之内完成,在 OI 中可能有用。
本文已经结束了。本文作者:ღꦿ࿐(DeepSea),转载请注明原文链接:https://www.cnblogs.com/Dreamerkk/p/18240465,谢谢你的阅读或转载!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步