给定一个 n , 有一个初始为 1 的整数 x , 每次有相同概率进行以下两个操作的其中一种:
问期望多少步操作可以使 x 大于 n , 输出期望步数模 998244353 的值。
其中 1≤n≤1018
如果从题解的角度来看, 这道题的解法可以说是极其不自然, 很多定义都有一种需要人类智慧的感觉。 所以我们从题目开始, 来一步步分析, 为什么会得到题解的做法。
首先, 分析数据范围, 不是一道可以加速的递推题就是一道结论题。 但因为结论题大多都能用递推加速做, 并且这个题目如果有结论, 样例也显现出这个结论是一个多项式, 而不是简单的特判加简短通式, 我们优先不考虑直接化结论, 毕竟最后就算是结论大不了就矩阵加速多个常数, 不是结论而想错方向浪费时间并且容易走进误区。
接着, 确认了大体算法方向后, 考虑是否答案的 f(n) 可以由前面相隔不远的几项, 比如 f(n−1) 、 f(n−2) ...递推过来。 当然, 最终经过某位勇敢者的亲身尝试, 证明这确实是正确的(直接粘一个 BM 的板子很符合我对网赛的想象), 可我们并不能从题目的暴力递推式和各种变形中证明这一点, 而中国的 OIer 们还要面对 NOI 赛制, 所以不建议在没有结论支持的时候使用 BM 这种算法, 当然, 论骗分, 直接码就行, 都不带怕的。
然后, 我们考虑两种最暴力的方法: 暴力枚举和简单 dp
先说一下简单 dp, 一般来说, 加速递推都是由简单 dp 的式子经过一系列的优化产生, 而这个过程中有一个比较烦人的点是, 如果有几乎 O(n) 个结束位置或者结束位置很难找到, 那么我们的加速就会变得复杂, 所以我们优先考虑能让结束位置比较优秀的状态。 那么我们定义 f(x) 表示 x 变到大于等于 n 的期望步数。 这样定义后, 可以发现, 初始状态是 f(x)=0(n≤x≤2(n−1)) 。 这里给了我们提示, 如果初始状态都是一段一样的, 那是不是前面的部分也可以分成几段, 每段都是一个结果或者有通式呢?
再来看一下直接暴力。 因为每一步都会使 x 更接近于结束状态, 所以我们可以写出一个非常无脑的暴力, 枚举所有的操作, 算贡献。 观察一种情况的贡献是 0.5ss , 其中 s 是这种情况的操作步数。 再观察最终的答案, 可以发现, 贡献的种类比情况数少很多, 这启示我们可能有些东西是可以合并的, 而 f(x) 又和 x 强相关, 那么我们可以联想到创造一个函数 g(x), 使得 g(x) 满足以下两点要求:
- 对于每个 f(x) , 都能使用少量的 g(x) 表示出来
- g(x) 是简单函数, 及不含 ∑ 、 Π 、 判断等
由于第一个要求, g(x) 和暴力的贡献大概率有着一些联系, 一般来说 g(x) 中会有贡献里比较特别、 难算、 有性质的项。 那这道题举例, 0.5ss 中的 s 太过普通, 感觉上就没啥用, 加上了甚至可能会妨碍我们化简。 而 2s 就很特别, 不是特别好算, 但又有一些性质, 所以 g(x) 中大概率会有类似的项。 本着在所有可能的地方加未知数的原则, 我们就可以合理推测出 g(x) 的最终形式应该是 g(a,b)(x)=a(12)bx 也许有人会好奇为什么这里没有常数项, 这是因为常数项可以用 g(a,0)(x) 来表示, 并且就算不是这种形式的 g(x), 也应该独立计算, 因为如果把常数项放在 g(x) 考虑就会出现分配问题, 化式子的时候容易出错。
注:本部分来自于 codeforces 赛后题解, 如有不懂, 可以点击此处跳转查阅
有了基本思路和算法方向, 我们就可以来推一推式子, 套一套优化了。
由于前面的分析, 我们有了 f(x) 是可以分成一段一段的, 每一段都有通式。 那么我们来看看如何求区间 (l,r) 的通式, 这个区间有通式又需要满足什么。
首先我们一步一步地推理, 如果 [l,r] 有通式, 那么需要知道什么?
在此区间内, 有:
f(x)=12f(2x)+12f(x+1)+1
=12f(2x)+12(12f(2(x+1))+12f(x+2)+1)+1
=12f(2x)+12(12f(2(x+1))+12(12(f(2(x+2))+…)+1)+1)+1
=r−x∑i=0(12)i+1f(2(x+i))+r−x∑i=0(12)i+(12)x+1f(r+1)
这里我们直接将所有括号暴力展开, 化简得出了这个式子, 然后我们再一项一项地考虑化简成几个 g(x)
首先 (12)x+1f(r+1) 就是 g(f(r+1)12,1)(x) , 注意这里的 f(r+1) 是常数, 并不是一个式子。
接着再看 r−x∑i=0(12)i 这一项, 这个比较简单, 直接上等比数列求和, 得到 g(2,0)(x)+g(−(12)k,−1)(x)
最后再来看最麻烦的一项, r−x∑i=0(12)i+1f(2(x+i))
由于这一项有多个 f(x) 求和, 而不同的 f(x) 之间的联系不太紧密, 不好化简, 所以考虑增加条件, 及 f(2(x+i)) 的 f(x) 是有同样的通项公式的, 也就是说 [2l,2r] 有通项公式。 这样化简起来就要方便许多了。
设 f(x)=∑fg(a,b)(x) , 那么有:
r−x∑i=0(12)i+1f(2(x+i))
=r−x∑i=0(12)i+1∑fg(a,b)(2(x+i))
=r−x∑i=0(12)i+1∑fa(12)2b(x+i)
=r−x∑i=0(12)i+1∑fa(12)2bx×(12)2bi
=∑fa(12)2bxr−x∑i=0(12)i+1×(12)2bi
=∑fa(12)2bx×12r−x∑i=0(12)i×(12)2bi
=∑fa(12)2bx×12r−x∑i=0(12)i(2b+1)
=∑fa(12)2bx×12r−x∑i=0((12)(2b+1))i
=∑fa(12)2bx×12×((12)(2b+1))r−x+1−1(12)(2b+1)−1
=∑fa(12)2bx×12(((12)(2b+1))r−x+1(12)(2b+1)−1−1(12)(2b+1)−1)
=∑fa(12)2bx×12(((12)(2b+1))r+1(12)(2b+1)−1×(12)−(2b+1)x−1(12)(2b+1)−1×(12)0×x)
=∑fa×12(((12)(2b+1))r+1(12)(2b+1)−1×(12)−x−1(12)(2b+1)−1×(12)2bx)
=∑fg(a((12)(2b+1))r+1(12)2b−2,−1)(x)−g(a(12)2b−2,2b)(x)
=g(∑fa((12)(2b+1))r+1(12)2b−2,−1)(x)−∑fg(a(12)2b−2,2b)(x)
我们将几个式子合起来, 就能得到
f(x)=g(f(r+1)12,1)(x)+g(2,0)(x)+g(−(12)k,−1)(x)+g(∑fa((12)(2b+1))r+1(12)2b−2,−1)(x)−∑fg(a(12)2b−2,2b)(x)
所以我们只要保证通过 O(log(n)) 次或者类似的比较少的次数递推得到 包含 1 的 [l,r] 即可。
考虑我们为了推式子给出的条件是得到 [l,r] 需要知道 [2l,2r] , 而最开始的一段已知的具有通项公式的段是 [n,2n−1] , 我们可以从这一段开始递推, 每次去求 [⌈l2⌉,⌊r2⌋] 就可以了, 另外注意一点, 由于初始的区间是 [n,2n−1] 所以我们能保证任意时候的区间 [l,r] 都有 r+1 属于上一次的区间, 也就是可以直接求出 f(r+1) 的值。
最后因为左端点要等于 1 需要 log(n) 次, 所以总时间复杂度是 O(Tlog2(n)) 或者 O(Tlog3(n) , 这里的时间复杂度差别在于快速幂。
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】