knapsack 题解
有
有若干个大小为
现在,你需要将这
两种方案不同定义为存在两个物品
方案数对
对于所有数据,满足
感觉是不是不止一次见到过这个题目了。在洛谷网校看到了一次,在云斗看到了一次。感觉是一个很有趣的题目。
做法
最小值
首先我们考虑如何算出最小值。
见到这个数据范围基本可以想到状压 DP(面向数据范围编程)。有一个较为朴素的暴力。
考虑
通常来说想要优化掉枚举子集有一个常见的技巧,记录最后一组的信息,考虑一次加入一个元素对于答案的影响。此时最后一组的信息包括最大值和子集和,此外你还需要记录代价。但是你发现这个结果并不是很好定义一个偏序关系,因此你只能考虑把一些信息放到状态里。比如说用
接下来说说这个神奇的正解。考虑折半搜索,先分成前后两半搜索,计算出不存在一组跨过中间的情况的最优解。接下来考虑跨过中间的最优解,令
具体地,我们定义两个数组
算法的流程如下:
- 对于每个
,预处理集合的重量和和代价(价值的最大值),将集合的和记作 ,代价记作 。即 。 - 枚举
。 - 枚举
,向 中加入 。 - 枚举
,记录 。 - 将记录下来的
对和 (请注意,不是 )排序,双指针转移到 。注意需要考虑右半边不选的情况,此时代价就是左边的最大值。
- 枚举
注意到两个枚举子集和的时间复杂度都是
注意到对于每个集合,它的子集和超集以及差集的重量是确定的,我们只需要提前把顺序预处理出来即可。
计数
我们注意到这个转移方法虽然可以求出最小值,但是直接计数会导致很多重复情况。考虑什么时候会重复计算,容易发现一个方案的分组会以不同的顺序加入导致重复计算。此时我们只需要钦定每个集合的转移顺序即可。比如,我们钦定最后一个加入集合的数必须要包含集合中编号最小的元素,这样每个方案的分组加入集合的顺序就是确定的(编号最小的元素从大到小)。
实现细节
在考虑跨过中间的情况,如果形如