KingdomAndDice
作者:陈俊锟 汪乐平 钟知闲 徐明宽
关键词:DP 背包 多重背包
题目简述
给出两个长度均为 的数组 和 ,其中 中有一些位置是 。你需要将 中若干个 修改成其他的数,要求最终的数组 满足:
中,所有数都是 之间的整数
所有正整数在和中的出现次数加起来不超过
在此基础上,设 为“随机两个 之间的整数 ”后,“”这一事件发生的概率,要求选择一种修改 的方式,使得 最小。
要求返回使得 最小的 。如果存在多个 ,输出较小的那个。。
算法一
首先,这两个数组的下标是没有意义的,因此我们可以把它们看成可重集合。
其次,我们可以将取到某个 的概率分别考虑。如果这个 ,那么它对 的贡献也确定了。如果不将它修改为非 的值,那么它对 没有贡献。否则,如果将 数组中的数排序,那么可以将 中分成若干段,其中每一段内取不管取哪一个数对 的贡献都是相同的。
设布尔数组 表示已经改掉了 个 时,是否存在一种方案使得 。我们把对 的贡献相同的数在一起考虑。那么问题就转化为了一个经典的多重背包问题,直接 DP 求解即可。
最后,我们就可以得出所有可行的 ,直接计算答案即可。
时间复杂度:。
关于时间复杂度
通常采用二进制分组的方法实现多重背包,即对于 数组中 到 连续的一段,如果 数组中满足 的 个数为 ,那么相当于 个大小为 的物品,将其拆成 个大小依次为 的物品(其中整数 满足 ),然后做0-1背包。这样的物品总数为 。用上述做法的时间复杂度和空间复杂度分别为 和 (如果使用滚动数组)。
虽然这个做法实际上已经可以通过了(毕竟 时 很小),但我们有时间复杂度更优的算法。以下给出两种算法:
1、修改状态表示。设 为从前 个段(段的意义如上文所述,同一段内取每个数对 贡献相同)中选取 个数改成 并使得 ,第 个段至少要选多少个数改成 。转移时只需枚举是否从第 个段中取数,同时注意第 段已取满的状态不能再转移即可。这样的时间复杂度和空间复杂度分别为 和 (如果使用滚动数组将 这一维滚掉)。
2、使用压位技巧。仍然用二进制分组的做法,注意到 是由 转移过来的( 为个数, 为贡献),因此相当于将 这个布尔数组进行移位然后做按位或运算,使用压位技巧即可做到 ,其中 为字长(通常 或 )。
算法二
其实并没有必要把改掉多少个放到状态定义里面……设 表示从前 个段中选取至少几个数改成 才能使得 ,转移时枚举从第段取数的个数即可。最后一个可行当且仅当不超过初始数组中的个数。这样暴力地实现就是的时间、的空间,加上滚动数组就是的空间,加上二进制分组变成01背包就是的时间了。(这里滚动数组并不需要开第一维大小为的二维数组,直接在一个大小为的数组上DP即可,可以节省一定的代码量)