2785 字
14 分钟
dkdk-上分日记
2026-06-26
无标签

基于项目:https://github.com/Lhanyu/cf-rating-diary 生成

《dkdk 上分日记》:六年新秀,WA 比 AC 还多#

dkdk,现居中国,注册于 2020 年 8 月 7 日。当前 rating 1478,specialist 段位,历史峰值 1728(expert),于 2026 年 5 月 21 日达成。

上分史:六次断档,两度蒸发#

dkdk 的账号有一点很特别:注册于 2020 年中,但第一场 rated 直到 2022 年 7 月才打——账号晾了将近两年。之后 73 场 rated 的曲线,可以用”六次断档”来概括。

第一阶段(2022.07):新手红利,五连涨。 从 0 到 1230,头五场净涨一千多分。特别是第三场 Round 807 单场 +249,四场之后已经踩在 pupil 和 specialist 的交界线上。但这波红利停得也快——第六场 +27、第七场 -22,涨势立刻熄火。

第二阶段(2022.08-2023.01):在 1100-1500 之间剧烈震荡。 单场 +148 的跳涨和单场 -115 的暴跌交替出现,这是典型的”能做一点题但完全没有稳定性”的阶段。最说明问题的是一组对比:Round 814 打出 +148 冲到 1363,紧接着 Round 815 就掉 40 分,然后 Round 816 又涨 45 分——涨跌完全看当天的题是否撞上舒适区。

第三阶段(2023.04-2023.09):冲 expert 未遂,力竭退潮。 2023 年初短暂触及 1530 后,立刻三连跌——-64、-56、-103,两个月内回到 1307。暑期重新爬回 1590,但 Div.2 的 C 题始终是道坎:cid=1856 C、cid=1860 C 各交了六七发,一题都没过。九月底 Round 901 又掉 59 分,然后……

第四阶段:第一次消失。 2023 年 9 月 30 日到 2024 年 10 月 14 日,整整 380 天没打一场 rated。从大一消失到大二,回归后 1513→1553,微涨 40 分——说明消失期间至少没退步,但也没进步。

第五阶段:第二次消失。 回归仅打了一个月,四场之后再次人间蒸发。2024 年 11 月 23 日到 2025 年 8 月 7 日,又是 257 天。这次回归更惨:1381→1311,净跌 70 分。手感这个东西,放一年真的会还回去。

第六阶段(2025.08-2026.06):最后的冲锋,也是最后的坠落。 这是 dkdk 最活跃的时期。2025 年 8 月 10 日 Round 1042 拿了一个惊人的 rank 34,单场 +287 从 1311 飙到 1598——这是 dkdk 生涯排名最高的一场(Div.3)。此后在 1400-1700 之间反复拉锯了将近一年,终于在 2026 年 5 月 21 日 Round 1099 冲到生涯峰值 1728。但峰值之后不到一个月,Round 1104 单场 -121,直接跌回 1478——从 expert 摔回 specialist,只用了四场。

数据层面,可以更清楚地看见这条曲线的质地:1417 次提交,AC 598 次,WA 584 次,AC 率 42.2%。WA 只比 AC 少 14 个。 平均每场过 2.75 题、每场交 6.76 次——做一题平均要搭 2.5 发进去。签到方面,A 题首发 AC 率 80%(76 场里 61 场首发过),B 题暴跌到 52%(69 场里 36 场首发过),有 8 场的 B 题打到最后都没过。爆零 2 场,全在前两场——毕竟那时候连编译器怎么用都还在摸索。

下面两个名场面,一个是 800 分的签到题,一个是 1400 分的构造题。两道题的难度差了 600 分,但 dkdk 在它们上面的表现惊人地一致:同样的反复提交,同样的越改越远,同样的靠判题机提示而不是自己想。这就是 42.2% AC 率的微观切片。

名场面一:800 分签到题的七连击(2155A “El fucho”,*800)#

这场是 2025 年 10 月 5 日的 Codeforces Round 1056 (Div.2)。A 题”El fucho”,一道 800 分的纯数学题——题目描述讲了一个双败淘汰制足球赛的故事,但学过双败淘汰的人看一眼就知道:总比赛场数恒为 2n-2。因为每支非冠军队伍恰好输两次(胜者组一次、败者组一次出局),每场比赛恰好产生一次败者组的淘汰,加上一场决赛——所有队伍的败者组淘汰次数之和是 n-1,加上胜者组的 n-1 个败者名额,恰好 2n-2 场。最水的写法是一行 cout << 2*n - 2

但 dkdk 在这道题上花了 111 分钟、7 发提交

时间线回顾:

t= 9.8min WA on pretest 2 passed=0 递归公式,公式错了
t= 21.2min TLE on pretest 1 passed=0 忘记注释掉 freopen,文件 I/O 导致挂起
t= 22.2min WA on pretest 2 passed=0 freopen 注释掉了,公式微调,还是错的
t= 26.7min WA on pretest 2 passed=0 换思路,用迭代模拟——但数的是轮数不是场数
t= 27.6min WA on pretest 2 passed=0 轮数方案的微调,仍是错的
t=110.8min WA on pretest 1 passed=0 终于开始统计每轮的场数(ans+=m/2+n/2),但留了调试输出!
t=111.3min Accepted passed=4 删掉 cout << n << ' ' << m << endl 那行,直接 AC

前两发是他试图用递推公式 f1(n) = f1((n+1)/2) + f2(n/2) + 1 来算,但两个递归函数互相调用的终止条件和公式都不对。第三发把 f2 改成只递推自身的变体 f2(n) = f2((n+1)/2) + 1——逻辑仍然是错的。第四、五发推倒重来,改用迭代模拟:while 循环每一轮更新胜者组和败者组人数,但他在统计”走了多少轮”而非”打了多少场”。每轮可能有复数场比赛,数轮数当然不对。

真正的悲剧在第六发:他已经找到了正确的算法——每轮累加 m/2 + n/2(胜者组和败者组各自的比赛数),循环结束后加上最后一场决赛。逻辑全对。但他提交的代码里留了一行 cout << n << ' ' << m << endl;——调试输出赫然打印到了 stdout,直接把答案格式污染了。WA on pretest 1——连样例都没过。

第七发只改了一件事:删掉了那行调试输出。直接 AC。

七发 111 分钟,从递归到迭代、从数轮数到数场数,算法层面跌跌撞撞终于摸到正确做法——结果第六发的唯一 bug 是忘了删 cout。他不是不会做,他是从第五发之后就没再认真读过自己改了什么,每次改了就跑、错了再改,判题机变成了他的调试器。

名场面二:连续六发内存超限的悬案(1855C1 “Dual (Easy Version)”,*1400)#

这场是 2023 年 7 月 29 日的 Round 889 (Div.2),距今不算近,但这个名场面值得挖——不是因为难,而是因为 dkdk 在其中展现的调试方式堪称教科书级别的”越改越远”。

题目”Dual (Easy Version)“:给定一个长度不超过 20、值域 [-20,20] 的数组,每次操作可以选 i, j 令 a[i] += a[j],要求在不超过 50 次操作内让数组变非递减。

正解思路很清晰:找到绝对值最大的那个元素,用自加(a[i]+=a[i])把它翻倍到绝对值超过 20(最多 5 次),然后用它作为”刷子”去刷其他元素。如果最大值是正数,从左到右做前缀和式的累加;如果全是负数,从右到左做后缀和。操作数上界为 5+2(n-1) ≤ 43,轻松卡在 50 内。

dkdk 的代码框架从一开始就选对了这个方向——但他一共交了 7 发,前 6 发全是 Memory limit exceeded on pretest 2

时间线:

t=25.9min MLE on pretest 2 passed=0 队列实现,Max=0/Min=0
t=47.6min MLE on pretest 2 passed=0 队列改 vector,其余不变
t=74.4min MLE on pretest 2 passed=2 把 Max/Min 初值改成 1111/-1111 ← 越改越远
t=78.5min MLE on pretest 2 passed=2 加了 if(q>50) exit(-1) 兜底,治标不治本
t=84.8min MLE on pretest 2 passed=4 改负数分支的循环条件——方向反了
t=91.6min MLE on pretest 2 passed=2 再改负数分支的条件——还是反的
t=93.3min Accepted passed=6 终于把 Max/Min 初值和循环方向全改对了

这六连 MLE 其实是 两个独立 bug 的排列组合,每个单独都能导致内存超限。

Bug 1:Max/Min 初始值反了。 第一版用 Max=0, Min=0 还算勉强能用(虽然遇到全零数组会在边界上略有歧义),但从第三发开始,他把初始值改成了 Max=1111, Min=-1111。数组元素的值域是 [-20,20],没有任何一个数能超过 1111 或低于 -1111。后果:Max 永远等于 1111,Min 永远等于 -1111,Max > 0 永远成立,代码永远进入”正数分支”。如果数组里根本没有正数(全零或全负),for(i..) if(a[i]>0) 找不到任何元素,变量 goal 保持未初始化,后续的 while(a[i] < a[i-1]) q.push_back(MP(i, goal)), a[i] += a[goal] 用的 goal 是个垃圾值——直接导致无限循环,vector 无限膨胀,MLE。正确的初始化应该是 Max=-1111, Min=1111,也就是他在第七发(AC)改成的样子。

Bug 2:负数分支的循环条件写错了邻居方向。 在处理全负数数组时,代码从右往左遍历(i 从 n-1 到 1),目标是用后缀和让数组变非递减。正确逻辑:检查 a[i] <= a[i+1],若违反则反复加 a[goal] 直到 a[i] > a[i+1] 被修复。但 dkdk 写的是 if(a[i] >= a[i-1]) continue; while(a[i] < a[i-1]) ...——他在跟左边的邻居比较,而不是右边的。当 i=1 时,a[0] 是未初始化的 0,而 a[1] 可能是个被加倍到 -20 的负数。-20 < 0 永远为真,a[1] += a[goal] 不断加一个负数给自己,越加越负,死循环——MLE。第五、六发把条件和循环都改写到了 a[i+1] 上,但方向的逻辑又反了(if(a[i] > a[i+1]) continue 跳过了需要修复的情况,while(a[i] > a[i+1]) 对着已经满足条件的方向猛加),仍然是死循环。直到第七发,他把方向和 Max/Min 同时改对,六连 MLE 才终于变成一次 AC。

两个独立的 bug,他修了 6 次才同时修对。 每一次都在动其中一个(有时还动错方向),没有一次两个一起审视。这是 42.2% AC 率的本质:不是不会做,而是改 bug 的方式停留在”改一处 → 交一发 → 看报错 → 再改一处”的穷举模式上。六发 MLE 之间他可能一次都没在本地跑过——本地随便一组数据都能触发死循环,一眼就能看出 vector 在疯涨。

尾声:六年,消失 637 天,WA 追在 AC 身后#

dkdk 的账号有一种奇怪的”未完成感”。2020 年注册,2022 年才开打。六年之间两度蒸发了总计 637 天——第一次回归微涨 40 分,第二次回归暴跌 70 分。从 2023 年就在冲 expert,冲到 2026 年 5 月才终于碰到。巅峰只维持了四场,就以单场 -121 的方式跌回 specialist。

而贯穿这一切的,是那两个几乎持平的计数器:598 次 AC,584 次 WA,中间只差 14。对于一个打了 73 场的人来说,WA 马上就要追上 AC 了

如果 dkdk 再来一次消失——以目前 1478 的 rating 和 -121 的下行趋势——下一次回来,WA 大概就真的超过 AC 了。

dkdk-上分日记
https://dk-qwq.github.io/blog/posts/dkdk-上分日记/
作者
dk-qwq
发布于
2026-06-26
许可协议
CC BY-NC-SA 4.0