🤖 Claude 苏格拉底式带教对话录原稿。每一步都是 mentor 提一个聚焦问题 → me 答 → 反复推进至完整算法。蓝色 callout 是 Claude,橙色 callout 是 zzy,原稿里 mentor 的反例 / 矫枉过正 / 用户吐槽全部逐字保留。
原题:CodeChef Starters 235 - Shift Game (Difficulty 2431)
背景
题目链接:https://www.codechef.com/problems/SHIFTGAME(CodeChef Starters 235,难度 2431)。
题面要点 :给两个长度为 N N N 的 01 串 A , B A,B A , B 。每轮把 A 1 ⋅ B 1 A_1\cdot B_1 A 1 ⋅ B 1 加进得分;若 A 1 = B 1 A_1=B_1 A 1 = B 1 则把 A A A 左移一位,否则把 B B B 左移一位。 Q Q Q 个独立查询,每个查询给一个 K i ≤ 1 0 12 K_i\le 10^{12} K i ≤ 1 0 1 2 ,问做完恰好 K i K_i K i 轮后的得分。约束 N , Q ≤ 1 0 6 N,Q\le 10^6 N , Q ≤ 1 0 6 , ∑ N , ∑ Q ≤ 1 0 6 \sum N,\sum Q\le 10^6 ∑ N , ∑ Q ≤ 1 0 6 。
下面按算法带教(Socratic)方式逐步推进。蓝色 标注首次出现的关键术语 / 符号。
第 1 步:理解题目
🤖 Note : Claude (Mentor)
先用你自己的话讲讲:
游戏规则一句话总结 :每次发生什么、得分加多少、谁会被左移?
关键观察 : A , B A,B A , B 都是长度 N N N 的 01 串,但 K K K 可以到 1 0 12 10^{12} 1 0 1 2 —— 你觉得这个数量级在暗示什么?
样例验证 : A = 01 , B = 10 , K = 5 A=01,\ B=10,\ K=5 A = 0 1 , B = 1 0 , K = 5 答案是 2,你能口算追一遍前 5 步、看看得分时刻在哪两步出现吗?
🧑 Note : zzy (Me)
okay,你先假设我理解题目了吧。
第 2 步:暴力解法 + 关键观察
🤖 Note : Claude (Mentor)
先把暴力解法的复杂度算清楚,再讨论怎么优化。
问 1(暴力) :最直接的模拟法,每次取 A 1 , B 1 A_1,B_1 A 1 , B 1 ,加分,根据相等与否 left_shift。
单次 iteration 是 O ( 1 ) O(1) O ( 1 ) 还是 O ( N ) O(N) O ( N ) ?(提示:left_shift 你打算怎么实现?)
K K K 次的总复杂度?
给定 K ≤ 1 0 12 K\le 10^{12} K ≤ 1 0 1 2 , N , Q ≤ 1 0 6 N,Q\le 10^6 N , Q ≤ 1 0 6 ,暴力为什么过不了?
问 2(关键观察 —— 这是这题的命根) :left_shift 不会改变字符串的多重集,只是改变"指针"。换句话说,字符串本身永远是那个串 ,变的只是"当前从哪里开始读"。
那么状态只需要两个量描述:
state = ( i , j ) \boldsymbol{\text{state} = (i,j)}
state = ( i , j )
其中 i i i 是 A A A 当前的起始指针, j j j 是 B B B 当前的起始指针,都模 N N N 。
状态空间一共多少?
这意味着什么?(关键词:循环)
🧑 Note : zzy (Me)
状态空间是 n × n n\times n n × n 的呀,是意味着循环,但是 n × n n\times n n × n 的循环我们不能接受啊。
第 3 步:寻找更紧的循环节
🤖 Note : Claude (Mentor)
完全对!这就是关键卡点。 N 2 N^2 N 2 个状态意味着循环节最坏 Θ ( N 2 ) = 1 0 12 \Theta(N^2)=10^{12} Θ ( N 2 ) = 1 0 1 2 ,确实接受不了。需要更深入的观察。
来看转移规则:
A i = B j A_i = B_j A i = B j : i + = 1 i\mathrel{+=}1 i + = 1 (只有 A A A 指针动)
A i ≠ B j A_i \ne B_j A i = B j : j + = 1 j\mathrel{+=}1 j + = 1 (只有 B B B 指针动)
问 3(最关键的观察) :不管哪种情况,每一步 i + j \boldsymbol{i+j} i + j 都恰好增加 1 。所以 K K K 步之后 i + j = K i+j=K i + j = K (在某个模意义下成立)。那么真正的"自由变量"其实只有一个 —— 给定 K K K ,只要知道 i i i 是多少, j j j 就定了( j = K − i j=K-i j = K − i )。
这把状态空间从 N 2 N^2 N 2 降到了 N N N ,对吗?
那循环节最多多长?
进一步:在前 N N N 步里, A A A 指针走过多少步(记作 a a a ), B B B 指针走过多少步( b = N − a b=N-a b = N − a ),有没有什么结构?
先回答前两个,第三个是下一阶段的钩子。
🧑 Note : zzy (Me)
哦,你的意思就是说,给定了 K K K ,知道了 i i i , j j j 就确定了是吧,所以说状态数只有 N N N 。okay,我现在已经理解这个状态为什么只有 N N N 个了,但是问题是, K K K 虽然给定了,但是每次都是不同的 K K K 呀,如果按照这样子,不就变成了 O ( Q N ) O(QN) O ( Q N ) 了吗?也不行啊。
🤖 Note : Claude (Mentor)
好问题,这是这题第二个关键卡点。但你这里把两件事混在一起了 —— 我们把它拆开。
关键再框 :轨迹是 query 无关的 。
不管你有多少个 query、 K i K_i K i 是多少,整个游戏只演化出唯一一条 序列 ( i 0 , j 0 ) , ( i 1 , j 1 ) , ( i 2 , j 2 ) , … (i_0,j_0),(i_1,j_1),(i_2,j_2),… ( i 0 , j 0 ) , ( i 1 , j 1 ) , ( i 2 , j 2 ) , …
query 只是问"这条序列在第 K i K_i K i 步时分数是多少",并不会改变序列本身。
所以正确的姿势是:先 precompute 一次,再 Q Q Q 个 query 各自 O ( 1 ) O(1) O ( 1 ) 查表 。总复杂度 O ( N + Q ) O(N + Q) O ( N + Q ) 而不是 O ( N Q ) O(NQ) O ( N Q ) 。
那现在真正的瓶颈在哪? K K K 可以到 1 0 12 10^{12} 1 0 1 2 ,整条轨迹至少要走到这个长度才能给出答案 —— 表存不下 也算不动。
所以问题精简成一个:
“一条 query 无关的、长度可以到 1 0 12 10^{12} 1 0 1 2 的轨迹,怎么用 O ( N ) O(N) O ( N ) 空间表示?”
来到 第 4 步 。
第 4 步:周期 = 把无穷轨迹压回 O ( N ) O(N) O ( N )
🤖 Note : Claude (Mentor)
单看这条轨迹本身(先不管 query),你觉得它是不是周期 的?为什么?
提示:动力系统的"状态"你刚才已经数清楚了 —— 在某种意义下只有 N N N 个值。有限状态 + 确定性转移 必然会推出什么?
🧑 Note : zzy (Me)
当然是周期的呀,现在的问题是,我们是用 ( i , j ) (i,j) ( i , j ) 来唯一标识一个状态的,我们现在要想办法把这个状态数量降下来,将带这个 O ( N ) O(N) O ( N ) ,你懂吗?之前降的这个方法被我否定了。
🤖 Note : Claude (Mentor)
对,正中要害。 ( i m o d N , j m o d N ) (i \bmod N, j \bmod N) ( i m o d N , j m o d N ) 是 N 2 N^2 N 2 个,太多;之前那个"给定 K K K 就只剩 N N N “是 依赖 query 的 ,对建立周期没用 —— 我们要的是 query 无关、真正只有 N N N 个状态 的视角。
提示:与其每一步 都跟踪状态,只在关键路标 处取样。最自然的路标: A A A 指针刚好走完一整圈的瞬间(即 i i i 是 N N N 的倍数)。
请你想一下:在这种” A A A 整圈"的瞬间, ( i m o d N , j m o d N ) (i \bmod N, j \bmod N) ( i m o d N , j m o d N ) 长什么样?只有几种可能?
🧑 Note : zzy (Me)
首先,这个 i , j i,j i , j 是 1-based 还是 0-based?我们姑且认为是 0-based 的吧,那么就是 ( N − 1 , j ) (N-1, j) ( N − 1 , j ) , j j j 不动保持为 0 可能的,已经移动了很多也是可能的吧?是我理解的有问题吗?
🤖 Note : Claude (Mentor)
对,按 0-based 走( A , B A,B A , B 索引 0 … N − 1 0… N-1 0 … N − 1 ,初始 i 0 = j 0 = 0 i_0=j_0=0 i 0 = j 0 = 0 )。但" A A A 走完整圈"不是 i = N − 1 i=N-1 i = N − 1 ,是 i = N i=N i = N —— 你把"指针指到最后一个字符"和"指针走过最后一个字符再回到起点"混了:
第一步开始时 i = 0 i=0 i = 0 (指着 A 0 A_0 A 0 )。
经过 N N N 次 i + = 1 i\mathrel{+=}1 i + = 1 之后 i = N i=N i = N ,此时 i m o d N = 0 i\bmod N = 0 i m o d N = 0 ,正好回到指着 A 0 A_0 A 0 —— A A A 真正走完了一圈 。
所以 anchor 时刻 i m o d N = 0 i\bmod N = \mathbf{0} i m o d N = 0 (不是 N − 1 N-1 N − 1 )。
至于你说的 j j j ,那部分理解完全对: j j j 可以是 0(一动没动),也可以是任何已经走过的位置,所以 j m o d N j\bmod N j m o d N 可以取 0 , 1 , … , N − 1 0,1,…,N-1 0 , 1 , … , N − 1 任一值。
合起来:anchor 时刻状态 ( i m o d N , j m o d N ) = ( 0 , 某个值 ) (i\bmod N,\ j\bmod N) = (0,\ \text{某个值}) ( i m o d N , j m o d N ) = ( 0 , 某个值 ) ,第二个分量在 { 0 , 1 , … , N − 1 } \{0,1,…,N-1\} { 0 , 1 , … , N − 1 } 里取。
✅ anchor 状态可以被一个量完全刻画,恰好 N \boldsymbol{N} N 种 —— 这就是我们要的 query 无关 N N N 态视角。
🤖 Note : Claude (Mentor)
继续推进。从当前 anchor 出发再走到下一个 anchor(即 A A A 再走完一整圈),这段时间里 j j j 一共增加了多少?换句话说,新 anchor 的 j m o d N j\bmod N j m o d N 与旧的差多少?
🧑 Note : zzy (Me)
这个需要走一遍才知道吧?
🤖 Note : Claude (Mentor)
对,必须走一段才知道,这道题就是没法直接给出 j j j 增量的闭式 —— 得 simulate。但你这一句话其实已经暗含了完整算法的样子:先模拟、在每个 anchor 处把当前 j m o d N j\bmod N j m o d N 记下来,等某个 j m o d N j\bmod N j m o d N 第二次出现,就抓到了周期。
那下一个问题(一个):你打算让 simulate 跑到什么时候 停下?也就是停止条件 / 跑完时手里都拿到了什么?
🧑 Note : zzy (Me)
这个好解决,使用记忆化就可以了,如果 j j j 的出现已经出现了周期性,就是已经回到了以前记忆化的点,那么就是找到了周期,就可以结束了。
🤖 Note : Claude (Mentor)
✅ 对的,停止条件就是 anchor 处的 j m o d N j\bmod N j m o d N 第二次撞上 —— 周期抓到、可以结束。
那下一个问题(一个):simulate 跑的过程中,你打算把哪些数据 存下来,使得后面任意一个 query 给一个 K K K (可以到 1 0 12 10^{12} 1 0 1 2 ),都能 O ( 1 ) O(1) O ( 1 ) 把得分算出来?
🧑 Note : zzy (Me)
在跑的过程中,vis 数组首先设置一个 − 1 -1 − 1 ,然后主要是记录一下这个 j j j 点是第几次走到的即可。
🤖 Note : Claude (Mentor)
cycle detection 这部分对了(vis[j\_modN] 存第几次走到,再次撞上就停)。但只这个还不够答 query —— 还要把得分本身 记下来。
simulate 边跑边存的两个数组 :
vis[j\_modN]:第几个 anchor 第一次走到 j m o d N j\bmod N j m o d N 。第二次撞上就抓到周期。
score[t]:第 t t t 个 micro-step 时的累积得分 (前缀和,逐步累加 A i ⋅ B j A_i\cdot B_j A i ⋅ B j )。
假设 simulate 最终在第 T T T 步检测到周期、周期前缀长度 P P P 、周期长度 L L L 、一个完整周期得分增量 Δ = ‘ s c o r e ‘ [ P + L ] − ‘ s c o r e ‘ [ P ] \Delta = `score`[P+L] - `score`[P] Δ = ‘ s c o r e ‘ [ P + L ] − ‘ s c o r e ‘ [ P ] 。那么对 query K K K :
ans ( K ) = { ‘ s c o r e ‘ [ K ] , K < P + L ‘ s c o r e ‘ [ P + ( ( K − P ) m o d L ) ] + ⌊ ( K − P ) / L ⌋ ⋅ Δ , K ≥ P + L \text{ans}(K) =
\begin{cases}
`score`[K], & K < P + L \\[2pt]
`score`\big[P + ((K-P)\bmod L)\big] + \big\lfloor (K-P)/L \big\rfloor \cdot \Delta, & K \ge P + L
\end{cases}
ans ( K ) = { ‘ s c o r e ‘ [ K ] , ‘ s c o r e ‘ [ P + ( ( K − P ) m o d L ) ] + ⌊ ( K − P ) / L ⌋ ⋅ Δ , K < P + L K ≥ P + L
查询 O ( 1 ) O(1) O ( 1 ) ,precompute O ( T ) = O ( N ) O(T) = O(N) O ( T ) = O ( N ) 量级( T T T 至多到第 N + 1 N+1 N + 1 个 anchor,micro-step 数被段数 × \times × 段长 bound 住)。
现在思路完整了:可以去写代码。建议:
主源文件放 src/Shift\_Game.cpp(与现有目录约定一致)
simulate 阶段就是个 while 循环,每步 O ( 1 ) O(1) O ( 1 ) ,遇到 anchor 时检查 vis
query 阶段直接套上面的分段公式
写完告诉我。
🧑 Note : zzy (Me)
确实还是要记录一手得分啊,这个主要是因为我们之前的重点都放在这个周期检测上了。
我有一个问题啊:纯模拟的话,这个时间复杂度是不是有问题呢?这完全有可能变成是 O ( N 2 ) O(N^2) O ( N 2 ) 的吧。
🤖 Note : Claude (Mentor)
你抓得对。pure simulation 单段最差能到 Θ ( N 2 ) \Theta(N^2) Θ ( N 2 ) ,整体 O ( N 2 ) O(N^2) O ( N 2 ) 走不动。
极端例子 : $A = $ 1010…(严格 0/1 交替), $B = $ 11…100…0(前半全 1 后半全 0)。 A A A 每次切字符, j j j 都得跨过 B B B 中间的"块边界"才能找到下个匹配 —— 一次跨越 ≈ N / 2 \approx N/2 ≈ N / 2 步。 A A A 一段内切换 N − 1 N - 1 N − 1 次,总 Θ ( N 2 ) \Theta(N^2) Θ ( N 2 ) 。
注意上面那个例子的"罪魁": A A A 严格 0/1 交替 —— A A A 每个位置都是"切字符",每次都强迫 j j j 重新跨过 B B B 的块边界。换个角度看:
问题 :反过来,如果 A A A 里有连续相同字符的段(比如 $A = $ 11100100,开头有一段 111),在这同字符段内部 相邻的两个 A-advance 之间, j j j 还需要 wait 吗?为什么?
🧑 Note : zzy (Me)
不需要啊,我们可以使用方法快进啊,就是把一整段看成一个数字,这样子每一次至少有一个段要前进。
🤖 Note : Claude (Mentor)
✅ 正中要害。run 内 j j j 不动 —— 处理一整个 run 只需要做一次 "在 B B B 里找下个等于 run 字符的位置",run 里剩下的 A-advance 都是即时 match。
所以原本一段 A 整圈是 N N N 个 micro-step,现在压缩成 R R R 个 run-step ( R = A R = A R = A 的 run 数)。配一张" B B B 里下一个 0/1 在哪"的辅助表,每个 run-step 是 O ( 1 ) O(1) O ( 1 ) ,单段 O ( R ) O(R) O ( R ) 。
但这事还没完。再回头看那个最坏例子: $A = $ 1010… 是严格交替, R = N R = N R = N 。单段 O ( R ) = O ( N ) O(R) = O(N) O ( R ) = O ( N ) ,cycle detection 至多 N + 1 N+1 N + 1 段 → 总 O ( N 2 ) O(N^2) O ( N 2 ) 。run-collapse 没把屋顶顶破,只是把常数压下来了。
问题 :现在的瓶颈是"一次 h h h (一段 A 整圈)需要 R R R 次 Φ \Phi Φ 复合"。能不能把一次 h h h 从 O ( R ) O(R) O ( R ) 加速到 O ( log R ) O(\log R) O ( log R ) ?这样总复杂度变成 O ( N log N ) O(N \log N) O ( N log N ) 。提示:A 的 run 字符是严格 0/1 交替 的——这意味着每次复合的两个 Φ \Phi Φ 都是 Φ 0 , Φ 1 \Phi_0, \Phi_1 Φ 0 , Φ 1 这两个固定函数轮替。
🧑 Note : zzy (Me)
哦,我懂了,严格 ( 0 , 1 ) (0,1) ( 0 , 1 ) 交替的话,那么理论上来说,我们采用这个状态表示法,只需要知道 i i i 当前处于 0 还是 1, j j j 当前处于 0 还是 1,那么就知道了之后的所有情况,因为后面是严格 0、1 交替的。
🤖 Note : Claude (Mentor)
✅ 你抓到了关键的一半:run 字符固定交替 → 首字符决定全部。
小偏差 : j j j 不是 0/1 二元, j ∈ { 0 , … , N − 1 } j \in \{0,…,N-1\} j ∈ { 0 , … , N − 1 } 是 B B B 的具体位置(你可能把" j j j 这个位置"和" B j B_j B j 这个位置上的字符"混了)。状态空间还是 N N N 个。
但"交替"这个性质不仅决定了 run 字符的排列,还有一个直接的算法学含义。
问题 :把 A A A 的连续两个 run 处理看成一组(先做 Φ c 0 \Phi_{c_0} Φ c 0 再做 Φ c 1 \Phi_{c_1} Φ c 1 )。第 1 组(处理 run 0 + run 1)复合出的函数,和第 2 组(处理 run 2 + run 3)复合出的函数 —— 是不是同一个 函数?为什么?
🧑 Note : zzy (Me)
我觉得啊,你说说看,你是不是过度数学化???你说这个东西和函数有什么关系啊???
🤖 Note : Claude (Mentor)
吐槽收到,“函数复合"这个词确实是装的——本质就是"跳法”。重说一遍:
处理一段 A 的 run,本质就是让 j j j 跳到 B B B 里下一个等于这个 run 字符的位置 。跳法只看 run 字符是 0 还是 1,是个"给定起点 j j j ,落到哪里"的固定规则。
把"连续处理两个 run"打包成一对 :
第 1 对:先跳到下一个 c 0 c_0 c 0 ,再跳到下一个 c 1 c_1 c 1
第 2 对:先跳到下一个 c 0 c_0 c 0 ,再跳到下一个 c 1 c_1 c 1
第 3 对:…
重新提问 :第 1 对的"跳法"和第 2 对的"跳法" —— 是不是完全一样 ?(“跳法”= 给定起点 j j j ,结束时落在哪里的那个固定规则。)
🧑 Note : zzy (Me)
其实跳法就是,如果字符一样,就将 A A A 的这段 run 跳过,记分,如果字符不一样,就先跳过 B B B 的这段 run,那么就回到了第一种情况。
🤖 Note : Claude (Mentor)
✅ 对——跳法的逻辑只看字符匹配(equal / unequal),完全不依赖 pair 序号,所以第 1 对、第 2 对、…、第 100 对都是同一个规则。
🧑 Note : zzy (Me)
A run skip 应该是指跳过 A A A 嘛,那跳过 A A A 的话那么 j j j 指针肯定是没动的呀。
还有这个 l a , l b , c A , c B la, lb, c_A, c_B l a , l b , c A , c B ,我们之前讨论过吗?这都是什么东西?
🤖 Note : Claude (Mentor)
两条都对:
“A run skip” 字面就是只跨 A A A 、 j j j 不动 ——你的解读对,我之前偷懒把 “A skip + B skip 预处理” 打包进同一个名字,不严谨。
l a , l b , c A , c B la, lb, c_A, c_B l a , l b , c A , c B 我之前直接拿来用没定义,我的锅。
术语 (统一一下):
l a [ k ] = A la[k] = A l a [ k ] = A 第 k k k 个 run 的长度; l b [ k ] = B lb[k] = B l b [ k ] = B 第 k k k 个 run 的长度
c A [ k ] = A c_A[k] = A c A [ k ] = A 第 k k k 个 run 的字符(0 或 1); c B [ k ] c_B[k] c B [ k ] 同理
R A , R B = A , B R_A, R_B = A, B R A , R B = A , B 的 run 数
把你之前描述的两个原子操作分别命名:
A run skip :状态字符相等时用, i + = l a [ k A ] i \mathrel{+=} la[k_A] i + = l a [ k A ] 、 j j j 不动 、得分 + = l a [ k A ] ⋅ c A [ k A ] \mathrel{+=} la[k_A] \cdot c_A[k_A] + = l a [ k A ] ⋅ c A [ k A ]
B run skip :状态字符不等时用, j + = l b [ k B ] j \mathrel{+=} lb[k_B] j + = l b [ k B ] 、 i i i 不动 、不得分
一个 round(从字符相等态起步) = = = “A run skip” + + + “B run skip”。
下一步问题 :一个 round 让 j j j 一共推进了多少?写成 j new = j old + ? j_\text{new} = j_\text{old} + ? j new = j old + ? 的形式。
🧑 Note : zzy (Me)
一个 round 让 j j j 推动了这个 l b lb l b 啊, j new = j old + l b [ k ] j_\text{new} = j_\text{old} + lb[k] j new = j old + l b [ k ] 。
🤖 Note : Claude (Mentor)
✅ 对。一 round 让 j j j 推进 l b [ k B ] lb[k_B] l b [ k B ] ( k B k_B k B 是 round 起始时 B B B 的 run 索引)。每 round 后 k A k_A k A 和 k B k_B k B 各 + = 1 += 1 + = 1 (分别模 R A , R B R_A, R_B R A , R B )。
下一步问题 : A A A 一整圈 = R A = R_A = R A 个 round。一整圈结束后 j j j 推进了多少?(用 l b lb l b 求和表达。)
🧑 Note : zzy (Me)
如果是 R A R_A R A 个 round,那么就是加前 R A R_A R A 个 l b lb l b ,当然,这个 l b lb l b 是循环往复的。
🤖 Note : Claude (Mentor)
✅ 对。一整圈结束后 j j j 推进 ∑ i = 0 R A − 1 l b [ ( k B + i ) m o d R B ] \sum_{i=0}^{R_A-1} lb[(k_B + i) \bmod R_B] ∑ i = 0 R A − 1 l b [ ( k B + i ) m o d R B ] ( B B B 跳了 R A R_A R A 次,每次过完 1 个 B B B run,下标循环模 R B R_B R B )。
下一步问题 :query 给一个 K K K (最坏 ∼ 1 0 12 \sim 10^{12} ∼ 1 0 1 2 ),我们最终需要算" r r r 个 round 之后的 ( k B , j , 得分 ) (k_B, j, \text{得分}) ( k B , j , 得分 ) ", r r r 也可能到 ∼ 1 0 12 \sim 10^{12} ∼ 1 0 1 2 。一个 round 的 transition 是 ( k B , j ) → ( k B + 1 m o d R B , j + l b [ k B ] ) (k_B, j) \to (k_B + 1 \bmod R_B,\ j + lb[k_B]) ( k B , j ) → ( k B + 1 m o d R B , j + l b [ k B ] ) ,单次 O ( 1 ) O(1) O ( 1 ) ,但循环 r r r 次 O ( r ) O(r) O ( r ) 完全过不去。怎么 O ( log r ) O(\log r) O ( log r ) 算这个"同一个 transition 重复 r r r 次"的结果?
🧑 Note : zzy (Me)
你是想用倍增吗?但是我感觉倍增有点困难啊,因为这个每次的 k B k_B k B 都不一样,那么这个增量也不大好计算吧。
🤖 Note : Claude (Mentor)
你的直觉准。per-round 倍增的 state 必须能完整决定下一段 transition 增量:
j j j 增量 = l b [ k B ] = lb[k_B] = l b [ k B ] ,依赖 k B k_B k B
得分增量 = l a [ k A ] ⋅ c A [ k A ] ⋅ c B [ k B ] = la[k_A] \cdot c_A[k_A] \cdot c_B[k_B] = l a [ k A ] ⋅ c A [ k A ] ⋅ c B [ k B ] ,依赖 k A k_A k A 和 k B k_B k B
所以 state = ( k A , k B ) = (k_A, k_B) = ( k A , k B ) ,空间 R A × R B R_A \times R_B R A × R B 最坏 Θ ( N 2 ) \Theta(N^2) Θ ( N 2 ) ,倍增表 O ( N 2 log N ) O(N^2 \log N) O ( N 2 log N ) 存不下。
下一步问题 :能不能换一个"更粗的 unit",让 state 空间变小?提示往"哪个量在固定 round 数之后会 reset / 回到原值"那个方向想。
🧑 Note : zzy (Me)
哦,我懂你的意思了, k A k_A k A 肯定是不需要的,你的倍增表,第一维是这个 k B k_B k B ,第二维是这个阶数,然后进行递推就好了。
🤖 Note : Claude (Mentor)
✅ 正中要害。“A 整圈”( R A R_A R A 个 round)这个 unit 上:
state 只剩 k B k_B k B ( k A k_A k A 在一个整圈内自动循环 0 , 1 , … , R A − 1 0, 1, …, R_A-1 0 , 1 , … , R A − 1 ,是固定序列,不算独立 state)
transition: ( k B , j , score ) → ( k B + R A m o d R B , j + Δ j , score + Δ s ) (k_B,\, j,\, \text{score}) \to (k_B + R_A \bmod R_B,\ j + \Delta j,\ \text{score} + \Delta s) ( k B , j , score ) → ( k B + R A m o d R B , j + Δ j , score + Δ s )
倍增表 jump[ $k_B$ ][ $p$ ] 存"从 k B k_B k B 出发、跳 2 p 2^p 2 p 个 A 整圈"之后的 ( Δ k B , Δ j , Δ s ) (\Delta k_B, \Delta j, \Delta s) ( Δ k B , Δ j , Δ s ) 。state 空间 R B ≤ N R_B \le N R B ≤ N 、 log N \log N log N 层,总 O ( N log N ) O(N \log N) O ( N log N ) ,能存。
下一步问题 :写出递推:jump[ $k_B$ ][ $p+1$ ] 怎么从 jump[ $\cdot$ ][ $p$ ] 算出来? Δ j , Δ s \Delta j, \Delta s Δ j , Δ s 怎么合?
🧑 Note : zzy (Me)
这个之前我就已经想过了,就是最经典的倍增表转移:从 jump[ $k_B$ ][ $p$ ] 和 jump[jump[ $k_B$ ][ $p$ ]. $k_B'$ ][ $p$ ] 的地方转移, Δ j , Δ s \Delta j, \Delta s Δ j , Δ s 应该直接加起来即可。
🤖 Note : Claude (Mentor)
✅ 经典递推没毛病。跳过这步,看更糙的 base case:jump[ $k_B$ ][0](一次 A 整圈,从 k B k_B k B 起步)。
前两个分量直接:
Δ k B = ( k B + R A ) m o d R B \Delta k_B = (k_B + R_A) \bmod R_B Δ k B = ( k B + R A ) m o d R B
$Delta j = $ 从 k B k_B k B 起 R A R_A R A 个连续 l b lb l b 的循环求和 → 前缀和 O ( 1 ) O(1) O ( 1 )
下一步问题 : Δ s \Delta s Δ s 才是难的一块:
Δ s ( k B ) = ∑ i = 0 R A − 1 l a [ i ] ⋅ c A [ i ] ⋅ c B [ ( k B + i ) m o d R B ] \Delta s(k_B) \;=\; \sum_{i=0}^{R_A - 1} la[i] \cdot c_A[i] \cdot c_B[(k_B + i) \bmod R_B]
Δ s ( k B ) = i = 0 ∑ R A − 1 l a [ i ] ⋅ c A [ i ] ⋅ c B [ ( k B + i ) m o d R B ]
朴素对每个 k B k_B k B 算需要 O ( R A ) O(R_A) O ( R A ) , R B R_B R B 个 k B k_B k B 总 O ( R A R B ) = O ( N 2 ) O(R_A R_B) = O(N^2) O ( R A R B ) = O ( N 2 ) ,不行。
怎么把每个 Δ s ( k B ) \Delta s(k_B) Δ s ( k B ) 压到 O ( 1 ) O(1) O ( 1 ) ?提示:注意 c A c_A c A 和 c B c_B c B 都是严格 0/1 交替 的(按 run 定义)。
🧑 Note : zzy (Me)
Δ s \Delta s Δ s 肯定也是没必要朴素计算的呀,知道 k B k_B k B 的字符 c B [ k B ] c_B[k_B] c B [ k B ] ,以及起始位置 A A A 块的 c A [ 0 ] c_A[0] c A [ 0 ] ,说白了,我们只需要看 R A R_A R A 中有多少个 c A [ i ] c_A[i] c A [ i ] 是 1,把它们和长度乘起来加上去就可以了。
🤖 Note : Claude (Mentor)
方向对,但有一个小裂缝: Δ s \Delta s Δ s 求和里是 c A [ i ] ⋅ c B [ ( k B + i ) m o d R B ] c_A[i] \cdot c_B[(k_B+i) \bmod R_B] c A [ i ] ⋅ c B [ ( k B + i ) m o d R B ] 两者都 = 1 = 1 = 1 ,不光看 c A [ i ] = 1 c_A[i] = 1 c A [ i ] = 1 。补一下:
c A , c B c_A, c_B c A , c B 都按同 parity 交替: c A [ i ] = c A [ 0 ] ⊕ ( i m o d 2 ) c_A[i] = c_A[0] \oplus (i \bmod 2) c A [ i ] = c A [ 0 ] ⊕ ( i m o d 2 ) 、 c B [ ( k B + i ) m o d R B ] = c B [ k B ] ⊕ ( i m o d 2 ) c_B[(k_B+i) \bmod R_B] = c_B[k_B] \oplus (i \bmod 2) c B [ ( k B + i ) m o d R B ] = c B [ k B ] ⊕ ( i m o d 2 ) 。所以两者要么 对所有 i i i 同时相等 ,要么 对所有 i i i 同时不等 :
若 c A [ 0 ] = c B [ k B ] c_A[0] = c_B[k_B] c A [ 0 ] = c B [ k B ] :AND 条件简化为 c A [ i ] = 1 c_A[i] = 1 c A [ i ] = 1 , Δ s ( k B ) = W \Delta s(k_B) = W Δ s ( k B ) = W ,其中 W = ∑ i l a [ i ] ⋅ c A [ i ] = ( A W = \sum_i la[i] \cdot c_A[i] = (A W = ∑ i l a [ i ] ⋅ c A [ i ] = ( A 中 1 的总个数 ) ) )
若 c A [ 0 ] ≠ c B [ k B ] c_A[0] \ne c_B[k_B] c A [ 0 ] = c B [ k B ] :永远不可能同时 = 1 = 1 = 1 , Δ s ( k B ) = 0 \Delta s(k_B) = 0 Δ s ( k B ) = 0
所以 Δ s ( k B ) ∈ { 0 , W } \Delta s(k_B) \in \{0, W\} Δ s ( k B ) ∈ { 0 , W } 二选一,由 c B [ k B ] c_B[k_B] c B [ k B ] 是否 = c A [ 0 ] = c_A[0] = c A [ 0 ] 决定。 O ( 1 ) O(1) O ( 1 ) 每个 k B k_B k B ,完美。
Preprocess 收尾 :base case O ( R B ) = O ( N ) O(R_B) = O(N) O ( R B ) = O ( N ) ,递推 O ( N log N ) O(N \log N) O ( N log N ) 。query 之前所有数据都齐了。
下一步问题 :query 来了,给你一个 K K K ( ≤ 1 0 12 \le 10^{12} ≤ 1 0 1 2 )。用 jump 表怎么走?描述一下流程(从 k B = 0 k_B = 0 k B = 0 、 j = 0 j = 0 j = 0 、 score = 0 \text{score} = 0 score = 0 起步,到答案输出)。
🧑 Note : zzy (Me)
不对不对,你把问题复杂化了。一个 round 中 B B B 肯定会前进一次、 A A A 也肯定会前进一次,那么如果块的 c A c_A c A 是 1 的话,必定在 round 中加上块长这个答案。我说的难道不对吗?
🤖 Note : Claude (Mentor)
你完全对,我搞复杂了。 错在我隐含假设"round 永远是 A skip 先 B skip 后"这个顺序,但 case B( c A [ 0 ] ≠ c B [ k B ] c_A[0] \ne c_B[k_B] c A [ 0 ] = c B [ k B ] )起始 state unequal,要先做一次额外 B skip 才进入 round。这次 B skip 让 c B c_B c B 下标多偏 1,刚好抵消 我担心的不匹配。重算:
case A ( c A [ 0 ] = c B [ k B ] c_A[0] = c_B[k_B] c A [ 0 ] = c B [ k B ] ,state 起始 equal):
round k k k 的 A skip 时 state = ( c A [ k ] , c B [ ( k B + k ) m o d R B ] ) = ( c A [ k ] , c A [ k ] ) = (c_A[k], c_B[(k_B+k) \bmod R_B]) = (c_A[k], c_A[k]) = ( c A [ k ] , c B [ ( k B + k ) m o d R B ] ) = ( c A [ k ] , c A [ k ] ) (同 parity 交替对齐)
Δ s = ∑ k l a [ k ] ⋅ c A [ k ] = W \Delta s = \sum_k la[k] \cdot c_A[k] = W Δ s = ∑ k l a [ k ] ⋅ c A [ k ] = W
case B ( c A [ 0 ] ≠ c B [ k B ] c_A[0] \ne c_B[k_B] c A [ 0 ] = c B [ k B ] ):
额外 B skip 一次: B → k B + 1 B \to k_B + 1 B → k B + 1 , c B [ k B + 1 ] = c A [ 0 ] c_B[k_B+1] = c_A[0] c B [ k B + 1 ] = c A [ 0 ] ,state 变 equal
之后 R A R_A R A 个 round 与 case A 同形:A skip k k k 时 state = ( c A [ k ] , c B [ ( k B + 1 + k ) m o d R B ] ) = ( c A [ k ] , c A [ k ] ) = (c_A[k], c_B[(k_B+1+k) \bmod R_B]) = (c_A[k], c_A[k]) = ( c A [ k ] , c B [ ( k B + 1 + k ) m o d R B ] ) = ( c A [ k ] , c A [ k ] )
Δ s = W \Delta s = W Δ s = W 同样
所以 Δ s = W \Delta s = W Δ s = W 永远成立、不依赖 k B k_B k B 。倍增表里这一项是常量,甚至不必每个 k B k_B k B 单存。
补一句关于 Δ j \Delta j Δ j :case A 是 R A R_A R A 个连续 l b lb l b 的循环求和;case B 多一个起始 l b [ k B ] lb[k_B] l b [ k B ] 。处理上把 case B 的"额外 B skip"放在 binary lifting 之前 单独算掉,之后均匀按 case A 走。
下一步问题(重发) :query 给一个 K K K 。用 jump 表怎么走?描述完整流程(从初始 state 到答案输出)。
🧑 Note : zzy (Me)
不对啊,我们为什么要假设 round 有先后顺序呢?把 round 看成 " A A A 走一步、 B B B 走一步",谁先谁后不重要,不行吗?
🤖 Note : Claude (Mentor)
对,你的抽象更省力。 把 round 看成 " A A A 进 1 个 run + + + B B B 进 1 个 run",顺序无关:
A A A 推进 l a [ k A ] la[k_A] l a [ k A ] , B B B 推进 l b [ k B ] lb[k_B] l b [ k B ]
state 在每 round 起始处都是 equal(归纳:相等态下 c A [ k A ] = c B [ k B ] c_A[k_A] = c_B[k_B] c A [ k A ] = c B [ k B ] , A , B A,B A , B 各跨 run 后 c A [ k A + 1 ] = c B [ k B + 1 ] c_A[k_A+1] = c_B[k_B+1] c A [ k A + 1 ] = c B [ k B + 1 ] 仍相等)
所以 A skip 时 state = ( c A [ k A ] , c A [ k A ] ) = (c_A[k_A], c_A[k_A]) = ( c A [ k A ] , c A [ k A ] ) ,得分 + = l a [ k A ] ⋅ c A [ k A ] \mathrel{+=} la[k_A] \cdot c_A[k_A] + = l a [ k A ] ⋅ c A [ k A ]
case A/B 唯一区别落在起始 state :case B 起始 state unequal,要先做一次 “pre-round B skip”(消耗 l b [ 0 ] lb[0] l b [ 0 ] micro-step、 j + = l b [ 0 ] j \mathrel{+=} lb[0] j + = l b [ 0 ] 、不得分),把 state 调成 equal;之后 rounds 完全统一。
所以最终算法骨架:
起始 state unequal ⇒ \Rightarrow ⇒ pre-round B skip 一次
倍增表跳大段(每段 = R A = R_A = R A rounds = = = 1 个 A 整圈, Δ s = W \Delta s = W Δ s = W 、 Δ j \Delta j Δ j = = = R A R_A R A 个连续 l b lb l b 的循环和)
剩余预算落在某 anchor 内,用 round 级前缀和定位到具体 round
落在 round 内,判断 A skip 还是 B skip phase,算 partial score
下一步问题 :query 给 K K K ,用 jump 表怎么走?描述完整流程(从初始 state 到最终答案输出)。
🧑 Note : zzy (Me)
不是,我就是要纠正你,哪里来的 “pre-round B skip”?为什么要先走一步?按照我的抽象,这一步是可以忽略的呀。
🤖 Note : Claude (Mentor)
你完全对,“pre-round B skip” 是我多加的,抱歉。 在你的 atomic round 抽象下根本不需要它——round 这个原子单元已经包含了 “B 推进 1 + A 推进 1”,本来就涵盖了"B 先动"那次。我下意识把 “case B 起始 B 先 skip 然后 A skip” 翻译成 “preprocess + 后续 (A-first) round” 两步,但其实就是 round 内部的两步,端到端 (k_A, k_B, j, s) 结果和"一个 round"完全一致。
校正算法骨架 :
无 preprocess ,直接从 round 0 起步走 binary lifting
每 anchor( R A R_A R A round): Δ s = W \Delta s = W Δ s = W 、 Δ j \Delta j Δ j = = = R A R_A R A 个连续 l b lb l b 的循环和(从当前 k B k_B k B 起)、 Δ m = N + Δ j \Delta m = N + \Delta j Δ m = N + Δ j
Within last anchor partial:用 round 级前缀和定位到具体 round
Within last round partial:唯一保留的 case 痕迹——case A(初始 state equal)round 内是 A skip 在前、case B 是 B skip 在前。不同顺序决定 partial 落在哪个 phase 上、得分是否要加 partial A skip 的贡献
下一步问题 :query 给 K K K ,用 jump 表怎么走?描述完整流程。
🧑 Note : zzy (Me)
跳吗就使用倍增常用流程,用全部的力气去跳,从高位到低位尝试,累加这个 Δ j \Delta j Δ j ,如果累加 Δ j \Delta j Δ j 超过了 K K K ,就说明超了,跳过这层。最后就是剩余 < R A < R_A < R A 的一段东西了。
🤖 Note : Claude (Mentor)
✅ 经典倍增 query 模板:从高 p p p 到低 p p p 试 jump[ $k_B$ ][ $p$ ],能塞进 K remaining K_\text{remaining} K remaining 就 take、否则 skip 这层。
小校正 :累加比较的不是 Δ j \Delta j Δ j ,是 Δ m \Delta m Δ m (一次 jump 消耗的 micro-step 总数)。一次 2 p 2^p 2 p anchor 跳里:
Δ m = 2 p ⋅ N ⏟ A 这边 + Δ j ⏟ B 这边 \Delta m = \underbrace{2^p \cdot N}_{A \text{ 这边}} + \underbrace{\Delta j}_{B \text{ 这边}}
Δ m = A 这边 2 p ⋅ N + B 这边 Δ j
Δ j \Delta j Δ j 只算 B B B 指针推进的距离。 A A A 指针跑完每个整圈是 N N N 步、 2 p 2^p 2 p 个整圈就是 2 p ⋅ N 2^p \cdot N 2 p ⋅ N 步。
倍增结束后剩 $K_text{remaining} < $ (一个 anchor 的 Δ m \Delta m Δ m )。这意味着:剩 < R A < R_A < R A 个完整 round + + + 可能还有半个 round。
下一步问题 :这剩下的 < R A < R_A < R A round 段怎么处理?用什么定位 K remaining K_\text{remaining} K remaining 落在哪个 round、然后落在该 round 的哪个 phase?
🧑 Note : zzy (Me)
这个 < R A < R_A < R A 段确实没什么思路啊,可以再提示一下吗?
🤖 Note : Claude (Mentor)
提示 :anchor 内 R A R_A R A 个 round,第 r r r 个 round 的 micro-step cost 是
round_cost ( r ) = l a [ r ] + l b [ ( k B + r ) m o d R B ] . \text{round\_cost}(r) = la[r] + lb[(k_B + r) \bmod R_B].
round_cost ( r ) = l a [ r ] + l b [ ( k B + r ) m o d R B ] .
你已知 K remaining K_\text{remaining} K remaining 严格小于这 R A R_A R A 个 cost 的总和。
问题归化成:给定一个长度 R A R_A R A 的非负序列,找 K remaining K_\text{remaining} K remaining 落在前缀和的哪一段 。
下一步问题 :用什么标准技巧 O ( log R A ) O(\log R_A) O ( log R A ) 解?
🧑 Note : zzy (Me)
我懂了,你的意思是继续细分——从大 R A R_A R A 到小 r r r ,小 r r r 之内就比较好解决了。这个可以用二分解决。
🤖 Note : Claude (Mentor)
✅ 二分 + + + O ( 1 ) O(1) O ( 1 ) 前缀 = O ( log R A ) = O(\log R_A) = O ( log R A ) 定位 round r r r 。
配 O ( 1 ) O(1) O ( 1 ) 前缀:
l a la l a 部分: prefix_la [ r ] \text{prefix\_la}[r] prefix_la [ r ] (从 run 0 起的标准前缀和)
l b lb l b 部分: cyclic_lb_sum ( k B , r ) \text{cyclic\_lb\_sum}(k_B, r) cyclic_lb_sum ( k B , r ) (从 k B k_B k B 起 r r r 个 l b lb l b 的循环和,用 prefix_lb \text{prefix\_lb} prefix_lb 配 floor / mod O ( 1 ) O(1) O ( 1 ) 算)
二分定到 round r r r 后, K remaining K_\text{remaining} K remaining 进一步缩到 round r r r 内部 ( < 1 < 1 < 1 个 round 的 partial < < < l a [ r ] + l b [ ( k B + r ) m o d R B ] la[r] + lb[(k_B + r) \bmod R_B] l a [ r ] + l b [ ( k B + r ) m o d R B ] )。这一 round 含 A skip + + + B skip 两个 phase。
下一步问题 :这两 phase 在该 round 内的顺序 由什么决定? K remaining K_\text{remaining} K remaining 落在第一个 phase 和落在第二个 phase 时,partial score 分别怎么算?
🧑 Note : zzy (Me)
循环前缀和实现上可以用 2 × 2\times 2 × 技巧解决。
partial score 分两种情况:
unequal (case B):先 B B B 动、再 A A A 动。 A A A 动时若 c A [ r ] = 1 c_A[r] = 1 c A [ r ] = 1 则记分。 A A A 推进步数 = max ( partial − l b [ … ] , 0 ) = \max(\text{partial} - lb[…],\ 0) = max ( partial − l b [ … ] , 0 )
equal (case A):先 A A A 动、后 B B B 动。 A A A 动时若 c A [ r ] = 1 c_A[r] = 1 c A [ r ] = 1 则记分。 A A A 推进步数 = max ( l a [ r ] , partial ) = \max(la[r],\ \text{partial}) = max ( l a [ r ] , partial ) (写错了,应是 min \min min )
🤖 Note : Claude (Mentor)
方向都对 ,两个小校正:
equal 情形 :A 推进步数 = min ( partial , l a [ r ] ) = \min(\text{partial},\ la[r]) = min ( partial , l a [ r ] ) ,不是 max \max max 。partial 还没满 l a [ r ] la[r] l a [ r ] 时 A 只推进 partial 步、满了就推进 l a [ r ] la[r] l a [ r ] 步、永远不超 l a [ r ] la[r] l a [ r ] 。
2 × 2\times 2 × 技巧 :把 l b lb l b 拼成长度 2 R B 2 R_B 2 R B 的数组 l b ^ \hat{lb} l b ^ 、对它做 prefix sum prefix ^ \widehat{\text{prefix}} prefix ,则 cyclic_lb_sum ( k B , r ) = prefix ^ [ k B + r ] − prefix ^ [ k B ] \text{cyclic\_lb\_sum}(k_B, r) = \widehat{\text{prefix}}[k_B + r] - \widehat{\text{prefix}}[k_B] cyclic_lb_sum ( k B , r ) = prefix [ k B + r ] − prefix [ k B ] 对 r ≤ R B r \le R_B r ≤ R B 直接成立(within-anchor 时 r ≤ R A ≤ R B r \le R_A \le R_B r ≤ R A ≤ R B 满足)。
至此算法链完整 :
preprocess ( l a , l b , c A , c B la, lb, c_A, c_B l a , l b , c A , c B 、prefix_la、 2 × 2\times 2 × prefix_lb、jump 倍增表): O ( N log N ) O(N \log N) O ( N log N )
per query (determine case + + + 倍增主循环 + + + within-anchor 二分 + + + within-round partial): O ( log N ) O(\log N) O ( log N )
总 : O ( ( N + Q ) log N ) O((N + Q) \log N) O ( ( N + Q ) log N )
N = Q = 1 0 6 N = Q = 10^6 N = Q = 1 0 6 时约 4 × 1 0 7 4 \times 10^7 4 × 1 0 7 量级,1–2s 稳过。
下一步 :去写代码,目标文件 src/Shift\_Game.cpp。注意几个边界:
A A A 全 0 / 全 1( R A = 1 R_A = 1 R A = 1 单调情形)
B B B 全 0 / 全 1( R B = 1 R_B = 1 R B = 1 单调情形)
起始 case 判断( c A [ 0 ] = c B [ 0 ] c_A[0] = c_B[0] c A [ 0 ] = c B [ 0 ] ?)
K = 0 K = 0 K = 0 边界(直接返回 0)
写完告诉我。
🧑 Note : zzy (Me)
书写过程中,是不是需要注意首段的特殊性?因为首段和末尾段不一定不同,可能出现字符趋同情况,破坏 0/1 严格交替的特性。
🤖 Note : Claude (Mentor)
漂亮的 catch ,这是真实的边界,我之前漏了。
问题 : A A A (或 B B B )当成线性串读 run 时,第一段和末尾段可能字符相同——但 A A A 实际是循环串 ,末尾 run 和开头 run 在循环视角下相邻,应合并成一个循环 run。否则会出现 c A [ R A − 1 ] = c A [ 0 ] c_A[R_A-1] = c_A[0] c A [ R A − 1 ] = c A [ 0 ] 这种"两个相邻同色 run",破坏前面所有" c A c_A c A 严格 0/1 交替"的归纳前提。
例 : $A = $ 00110,线性 runs [ 00 , 11 , 0 ] [00, 11, 0] [ 0 0 , 1 1 , 0 ] , R A = 3 R_A = 3 R A = 3 (奇)。循环视角下末尾的 0 和开头的 00 应合并成 000,循环 runs [ 000 , 11 ] [000, 11] [ 0 0 0 , 1 1 ] , R A = 2 R_A = 2 R A = 2 (偶)。
处理 :preprocess 时检查 A [ 0 ] = A [ N − 1 ] A[0] = A[N-1] A [ 0 ] = A [ N − 1 ] ?
是:合并线性末尾 run 和第一个 run, R A − = 1 R_A \mathrel{-=} 1 R A − = 1 ,新 $la[0] = $ 末段长 + + + 首段长
否:直接用线性 runs
合并后 R A R_A R A 必为偶(或 = 1 =1 = 1 当 A A A 单色)。 B B B 同理。 O ( N ) O(N) O ( N ) 一次性处理,不影响主算法复杂度。
边界清单补一条进去,继续去写代码。
🧑 Note : zzy (Me)
(待回答 …)
心路历程(WA → TLE → AC)
最初的代码是按这份 mentor.tex 推导出来后写出来的。提交三次才稳:
🐛 WA (提交 1268875897 )—— 倍增表外层循环写反 :原写法外层 i 内层 p,但递推 jump[i][p] = jump[i][p-1] ∘ jump[jump[i][p-1].blockB][p-1] 读到的 jump[i'][p-1] 在 i’ 那行还没轮到时是默认 0,jb / score 双重少算。对拍 seed 11 命中 ,K=45 应得 16 实出 11。修:把外层换成 for (int p = 1; p < LEV; ++p)。教科书级倍增坑。
🐛 WA 同发现 :「B 全 1、A 非常数」特判分支从「A 全 1」分支复制粘贴错——内层条件 if (B[0] == '1') 永真,把所有 K 直接吐出。正确逻辑:B 永远 = 1,A 走到第一个 0 就卡死,得分 = min(K, la0)(A 前导 1 的长度)或 0(A 前导 0)。
⏱️ TLE (提交 1268882247 )—— 修对算法后 subtask 1/2 卡 3.01s。三处常数:(a) vector<vector<BlScJb>>(Rb, vector<BlScJb>(42)) 是 Rb=10⁶ 次小堆 alloc + 24MB vector header;(b) cal_ans 用 i128 比 ull 慢 2-3×(实测求和上界 4×10¹⁸ 仍在 ull 内);© 42 层倍增写死,N=10⁶ 时其实 21 层够用(2^p · N > K_max 的层根本 take 不到)。
✅ AC 1268899055 :层主序单块 alloc vector<BlScJb> jump(LEV * Rb)、ull 替换 i128、LEV 自适应。本机 N=Q=10⁶ 接近全交替最坏 case 0.9~1.0s ,CodeChef 全 13 subtask 通过。
📌 教训提炼 :
倍增 / ST 表,外层永远是 level p ,先填整层再算下一层
对拍是检测算法 bug 的硬通货——上一轮没对拍只看代码漏掉了
WA 修完先看 TLE 风险:i128 / 嵌套 vector / log 层数硬编码都是常数因子噩梦
AC 代码
提交记录:
源码 Shift_Game_clean.cpp 见本节下方折叠块(由 raw API 附加)。
📎 附件
mentor.pdf —— 完整带教对话录的 PDF 编译版(见末尾 PDF 块)