官方题解,较月赛讲评时使用的有较大修改。
题意:给定一个长度为 的 01 串的前 个字符,求添加后 个字符使得整个 01 串所有长度为 的子串的二进制值的方差最小。,数据随机。
对正解没什么帮助的部分分:
:手玩发现输出和输入一模一样,所以当个复读机就能得到 分了!
:枚举后 个字符计算方差,时间复杂度 ,期望得分 分。
正解思路:首先推一下方差的式子可以得到一个变形
那么我们要做的就是最小化右边这个式子。考虑拆贡献,先假设后面的位都是 ,那么每一位变成 都会对方差构成一个贡献,有两位都是 会产生一个联合贡献。
在下面这个表格中,每一列的 表示左起第 个长度为 子串的二进制值,“总和”表示这 个数的和。“初始值”那一行代表了后面的位全都是 的时候对应数的值,其中 表示是输入的 01 串的二进制值, 表示输入的 01 串去掉前 位再左移 位的二进制值, 就是这 个初始值的和。每一行的“第k位”表示左起第 个字符变成 后,对应每一列的增加量。所以,每一列最终的值等于这一列“初始值”那一行的值,加上后面所有为 的字符位对应行的值。
总和 | |||||||
---|---|---|---|---|---|---|---|
初始值 | |||||||
第 位 | |||||||
第 位 | |||||||
...第 位... | |||||||
第 位 | |||||||
第 位 |
可以看出这个表格是一个斜三角的形式,对角线是 ,每向右上角一斜行就翻倍。于是,我们的方差变形式的右侧就等于前 列的最终值的平方的和,乘以 ,再减去“总和”一列最终值的平方。把所有平方都打开,整理出只和某一位有关的项和某两位的乘积项,就能整理出每一位变成 的单独贡献,和某两位同时变成 的联合贡献:(下面省去长长的推式子环节,因为不是题解重点)
第 位的单独贡献:
第 位的联合贡献(注意不是第 位):
转化成图论模型就是一张图有点权和边权,找一个诱导子图的点权边权和最小。如果点权和边权都任意输入则这个问题是 NPC 的,不能直接做。但是转换成这样可以改成搜索算法,裸的搜索不剪枝计算方法得当也可以达到 的复杂度,期望得分 。
发现联合贡献全是非负数,可以利用这个性质剪枝。具体方法是,设某一位的单独贡献加上它和之前选过所有位的联合贡献为其当前贡献,如果当前贡献为正,这一位的总贡献一定为正,那么一定不选。如果当前贡献加上它和后面所有位的联合贡献仍然为负,这一位的总贡献一定为负,一定要选。加上剪枝后的搜索很快,开__int128
就能跑 ,期望得分 。随便写个高精就能跑更大的范围,但是如果要通过本题,需要一些实现细节上的优化:所有使用高精乘法的地方都可以用二进制位移取代,于是使用二进制压位高精,不需要用高精乘高精。利用二进制状压记录选过的位,那么计算所有联合贡献的和也可以常数次运算得到,不需要枚举每一位。用到的所有操作都是线性复杂度的操作,可以做到搜索单次转移复杂度为 ,也就是常数次高精计算的复杂度。最终要实现的高精大概是:无符号,支持加减法,二进制位移,乘int
,比大小。
关于复杂度分析:其实我并不会计算这个算法的具体复杂度……实际测试中,dfs 次数大约是 ,dfs 分叉次数大约是 。如果按照这个数据,算法的实际复杂度接近 。如果哪位同学证出了严格复杂度,还有非随机数据下的复杂度上界,欢迎来讨论。