牛客练习赛137 C&E 题解
牛客练习赛137 C&E 题解
非常有启发性的题目啊。赛时过了 C,赛后看 E 题解一直没看懂,在 U 群热心网友 @edge 的帮助下总算搞懂了。但代码实现有点复杂,交上去只能通过 93.75% 的数据,用别人的 AC 代码拍了几千组都没有拍出错误,最后不得已向出题人求助获得数据,但出题人非常热心,直接指出了我的代码中取模没取干净,最后终于通过了
先从 Easy Version 看起。首先,根据期望的线性性,我们可以分别计算每个 \(a_i\) 操作之后的期望值。设 \(p_i\) 表示恰好操作 \(i\) 次的概率,易知 \(p_i = \dbinom{k}{i} \cdot 2^{-k}\)。设 \(f(s, i)\) 表示整数 \(s\) 修改 \(i\) 次以后的值,设 \(g(s)\) 表示经过 \(k\) 次操作以后 \(s\) 的期望值,则
\(p_{i}\) 容易在 \(O(k)\) 时间内计算,我们接下来主要关注 \(f(s, i)\)。如果对每个 \(a_{i}\) 暴力计算,则时间复杂度为 \(O(nk)\),无法接受。
如何优化?观察修改的式子,发现有一项是 \(s \operatorname{and} m\),这启示我们可以只关心 \(s\) 的较低位。具体来说,设 \(c\) 是满足 \(2^{c} \le m\) 的最大整数(即 \(m\) 二进制下的最高位),\(\operatorname{low}(s)\) 表示 \(s\) 的较低 \(c\) 位的值,\(\operatorname{high}(s) = s - \operatorname{low}(s)\)。可以发现:\(g(s) = \operatorname{high}(s) + g(\operatorname{low}(s))\)。感性理解,较高位不参与修改操作,可以在一开始就分离出来。所以只需要对 \(2^{c} = O(m)\) 个值计算 \(g\) 数组,暴力模拟即可,时间复杂度 \(O(mk)\)。代码
对于 Hard Version,\(O(mk)\) 的时间复杂度也无法接受了。我们可以尝试推广先前的做法,即每次修改操作中都把高位分离出来,这样只用考虑低位,而不同的低位只有 \(O(m)\) 个。考虑到 \(m\) 远小于 \(k\),从这个角度出发或许能导出时间复杂度更低的做法。
具体而言,我们考虑对 \(s\) 进行 \(k\) 次修改得到的长度为 \(k + 1\) 的整数序列(包含初始值),设修改 \(i\) 次以后的值为 \(b_{i}\)。我们只保留 \(b_{i}\) 的低位,也就是说 \(b_{i} = \operatorname{low}(f(s, i))\)。考虑 \(b_{i}\) 对 \(g(s)\) 的贡献。显然,\(b_{i} \cdot p_{i}\) 是贡献的一部分。但如何考虑高位的贡献呢?对 \(b_{i}\) 进行一次修改,变成 \((b_{i} + (b_{i} \operatorname{and} m) + x)\),但 \(b_{i + 1}\) 只保留了低位,高位的贡献丢失了。关键的地方在于:高位的贡献会一直保留,无论之后修改多少次。设 \(sum_{i} = \sum_{j = i}^{k} p_{i}\),即至少修改 \(k\) 次的概率,那么高位的贡献为 \(sum_{i + 1} \cdot \operatorname{high}(f(s, i + 1))\)。
对 \(s \to \operatorname{low}(s + (s \operatorname{and} m) + x)\) 这种转移关系建图,则图中有 \(2^{c} = O(m)\) 个点。由于每个点恰有一条出边,所以图是基环树森林。因此从任意一点出发走 \(O(m)\) 步就能走到环上,我们可以考虑一次性计算环上的某个点的贡献。设从 \(s\) 出发走 \(L_{0}\) 步可以走到环,环的长度为 \(L\),途径的路径为
(忽略了后续一直在环上走的过程。)
对于不在环上的点,直接套用公式计算贡献。对于环上的某个点 \(b_{i}\),它同时是路径上的第 \(i, i + L, i + 2L,\cdots\) 个点,所以它的贡献为
这里我们要处理 \(p_{i}\) 和 \(sum_{i}\) 的求和。用数学方法导出封闭形式是不好做的,我们只能 \(O(k)\) 递推。对于相同的环长,使用的和是相同的,无需重复求。而根据经典结论,由于环大小的和不超过 \(2^{c}\),所以不同的环长只有 \(O(\sqrt{m})\) 种,因此计算这些和的总时间复杂度为 \(O(k \sqrt{m})\)。(题解说环长一定是 \(2\) 的非负整数次幂,根据这个结论时间复杂度为 \(O(k \log m)\),但没有给出证明。)
综上所述,我们在 \(O(m^{2} + k \log m)\) 的时间内解决了问题。代码