noip2026日志

交流总结

训练方向

  1. 对题目做完,试图通过【代码构建的复杂性】【对比赛节奏的影响】等方面评估其在 noip 中出现的概率(判断题目价值)。

  2. 每天写总结:归因训练问题(对题目/比赛),分析主要瓶颈,规划以后训练。

  3. 应该有目标意识:你准备做什么,服务于什么目标,二者关系是否紧密。(微观表现是问题意识,宏观表现是战略意识)。

问题意识

通常需要对大量素材的观察,才能发掘出一个问题背后的主要原因。因此平常训练想方设法记录自己面对问题的反应过程(比如写代码采取录屏)。

面对问题,应该:描述现象,归纳原因(逐渐增多),提出针对性措施,校验措施是否和问题对应。

比如代码:

  1. 对自己编码过程录屏,然后记录自己每段时间在做什么,以提供可供观察的素材。

  2. 直接定位到问题产生的最初时刻和原因(想假了,思考缺乏代码细节,前后逻辑混乱,笔误,ub,节奏不合理导致专注力下降等)。

  3. 提出针对性解决措施(措施需要深挖,直至变得具体可执行)。

比如想题:

  1. 尝试抽象出一道题背后的本质结构(显然不只算法),并总结未来可能有类似形态的题(适用范围等?)。

时间分配:

  1. 训练完,分析哪些训练重要,哪些不重要,为什么。

  2. 比赛完,考虑哪些时间因为什么原因被浪费了。

  3. 分离想题和做题。前者可以在任何地方,后者只能在电脑上。如果出现二者不匹配的现象,考虑规划好/难想或者好/难写的题目调整节奏。

  4. 应该合理安排训练计划,做到强度合理(强度太小显然不行;强度太大可能会懈怠,并且不利于调整节奏)。

  5. 规划学习和休息时间。专注时间必须用于完成既定任务,而不可分心;休息时间进行限时。

具体建议

  1. 调试代码:调试代码定位到错误在哪后,构造一个局部的样例对着模拟。要么没想清楚,样例不够强,不然就是模拟错了。

  2. 卡常:当不知道代码怎么写的更快时,将 ai/别人的优秀代码 和自己的代码对比。通过记时函数定量分析时间差异。

  3. 想题的目的是积累 trick,切忌“大跃进”,应该循序渐进。(因为难题的学习难度更高,你需要大量的相关练习才能形成一个思路,而循序渐进可以较快抓住核心思路,反而效率可能更高)。


9.17

A. Seats 3

问题:第一次提交 wa 了一发。

原因:写到中途才发现 \(n\) 要变成 \(2n+2\),于是修改,但是忘记修改空间。

位置分析:j 组 t1。

B. Jeweler

一遍过。

位置分析:过于模板,可能 j 组 t1/2。

C. Clothes

最开始思考完不会多项式做法,只会用 queue+bitset 暴力搜。

偷瞄一眼标签发现确实有 bitset。

然后写代码+调试总共 1h。

最后发现确实过不去,看完题解才发现答案至多 \(6\) 个数,dfs 是对的(因为 bfs 要记录前驱,并且还有哈希(这没法用 bitset 优化),这导致时间空间复杂度都劣于 dfs)。

问题:代码,没有想到做法。

原因:

代码等待总结。

没有想到做法:在没有充分思考的前提下相信没有复杂度保证做法,纯粹是赌。

D. River Rafting

5 分钟想到大题 dp 思路。十几分钟想到 可以对点权做子树 ckmin,然后差分贡献。接着想至 40 多分钟,放弃看题解。发现没有观察到一次操作至多覆盖整个子树不劣,并且没有观察到如果用 vector,一个时刻的 sz 和是 \(O(n)\) 的。

问题:思考时间过长。

原因:缺乏对冗余状态的优化能力和空间优化能力。

位置:noip t2(偏简单)。

【数据删除】。

和同学进行了小范围讨论。自己初步得出了一个很难写的做法,询问 ai 得到了很高明的好写的做法。

位置:noip t3/4。

主要瓶颈:

两道题调代码和无效思考的时间过长,并且一天的训练时间比想象的要少,所以只完成了 30% 左右的训练计划。

以后规划:

观察 1h 的调代码录屏,看看能得出什么结论。

一个题 10 min 没有有效思考强制看题解。

在上课时间内保持专注,下课休息。

明天先继续完成今天剩余的训练计划。

9.18

问题

具体题目

D. River Rafting

完成代码。30 min。

问题:编写代码的平均速度下降。

原因:因为担心后续调试时间过长,编写代码的过程更专注谨慎了。

解决:希望通过更多的练习以熟悉更加专注的编码过程(直接提高编码速度是不可取的,调试代码的时间往往会比编写代码时间长)。

E. 新桥

总共思考了 30 min,有了很复杂的四只 log 的做法,无法通过代码验证。

问题:过长的思考占据了练习时间。

解决:尝试将写题和想题分离。

C - Greedy Customers 2

之前想过得到 \(O(n^5)\) 的做法。观察题解后得到了两个优秀的做法。

问题:中途同学前来交流题目,并未完成编码。

解决:训练计划以外的题目先进行收集,后续再思考,等思考成熟再放入计划。

1044 - CSP 2024 提高级第一轮

训练瓶颈

  1. 代码视频尚未观看,今晚必须完成。
  2. 时间效率低下。
    表现:今天代码只编写了一道,其余思考的两道题目也没有独立想出来的。
    今日原因:
    1.中午有一个同学上来,要和我分享题目,于是本来编写 C 的代码被挪用思考。
    2.下午有一个熟悉的同学上来,于是松懈聊天,少做了一套初赛题。
    两日原因:因为开始规范训练,因此改变了空余时间思考题目,转而变成训练时专门思考的流程。因此减少了思考的时间。

训练展望

  1. 集中精力,先解决代码问题。总结题目主要总结代码问题,想不出题的问题先放下,只是在真正理解做法后,尝试抽象出trick,整理到笔记。

  2. 提高时间利用率。专注时间必须用于完成既定任务,而不可分心;分离想题和看题解和做题,比如今晚就阅读明天训练题目,然后在空余时间思考,电脑前看题解/做题。

9.19

具体问题

1042 - CSP 2023 提高级第一轮

P12948加训

会了 \(O(n \log n)\)。代码打了 \(30 min\)。这样代码上基本没有问题。

abc

问题:

这场打的非常区。其中一个重要的两个原因是:D 题调了 15min,F 题调了 20 min。

其中 D 题是中途写到一半,发现要对 a 数组做前缀和,因此加入这部分,但是忘记了必须要在排序后再做前缀和。

F 题是最开始想三角形前缀和的时候,忽略了边界的两个三角形并非都有值,因此公式并非均正确,最后通过调试输出+手玩小样例才解决。

原因:

D 题的原因属于逻辑混乱(应该先排序再做前缀和),最开始没有规划好逻辑框架,写一半再进行修改,就容易前后逻辑混乱;并且调试时我自认为代码很简单,不会出错,所以注意力放在思路出错上,浪费了更多时间。

F 题的原因属于思考后验证正确性的时候,忽略了 cornercase。

解决:

  1. 逻辑混乱就先写框架,再往里填。
  2. 调试时重点出错,就逐个原因排查,而不是只在一个部分使劲(比如逻辑混乱/代码写错,调试时就通过静态调试解决;初始思路出错/cornercase就通过思考/手玩样例解决。)

非具体问题

观察到:今天训练时间有 \(1\) 个上午和 \(1\) 个晚上(初赛消耗了一个下午),但是我做的事情相较周中更少了(直接体现就是只完成了一道题的代码,没有独立想出新题/阅读题解)。

直接原因是花了半个上午/晚上在弹琴/刷 b 站,根本原因是我没有很好平衡休息与学习的关系,使得休息占用了太多时间。

因此考虑开始规划休息时间,初步想法是禁止在 b 站刷短视频,每天弹琴 20 min。周末弹琴/运动 至多 2h。

9.20

具体问题

C - Greedy Customers 2

总共 20 min。

框架 6 min。10 min 写代码,5 min 调试。

其中遇到的问题是:

  1. 写代码过程中,发现 dp 转移式中的 \(\dfrac{1}{t!}\) 没有相应的预处理,最开始想着直接预处理,打完代码才发现预处理 \(\dfrac{1}{t}\) ,然后转移过程中从小到大枚举 \(t\) 的时候一步步乘上去更优,于是删掉重打。

  2. 调试时,发现清空 dp 数组的部分放在了统计答案之前,原因是写完框架填代码的时候,写到 \(init_f\) 的部分将其误认成 \(clear_f\),后面写到 \(clear_f\) 的部分时发现问题,于是其搬下来,下意识的搬在了统计答案前面,忽略了改动顺序对其它部分的影响。

  3. 调试部分,写着写着发现多重集计数,最后忘记乘 \(n!\);并且最后发现题目要求概率而非总和,因此忘记除总方案数。

原因:

  1. 框架写的逻辑不够严密,写到 dp 部分只是简单地为转移部分预留了位置,而没有考虑其需要用到什么辅助数组;后期写到发现缺失时,没有先想清楚并校验成功再写,而是想当然得出一个修改方案,写完才发现有更优的。

  2. 对代码进行意料之外的改动(比如搬迁,加入新代码等),没有考虑前后的逻辑关系,导致先后顺序出错。

  3. 对式子不够明晰,只有大概的一个感觉;没有读清题目。

P130115 道路修建

这个是重头戏。写了 1 h 5 min。

写了 10 min 框架。17 min 代码,调试很久。大致进行了 \(5\) 次调试尝试。

问题:

  1. 代码时间偏长,因为写到一半才发现,之前预想的前缀最大值是不成立,因为有 \(\ge \max(i,j)\) 的限制,作用的实际上是一段区间,因此需要扫描线+可删堆,所以重新规划了这部分框架(虽然没有校验正确性,但是幸运的是这回是正确的)。

  2. 出现了 3 处笔误(扫描线删除的时候,按照习惯下意识加上了负号;for 循环的 >= 和 <= 写反了;二分的取最大的还是最小的写反了(因为代码有两次二分,第二次部分下意识取了和第一次部分一样))。这里花费了 5 min。

  3. dp 逻辑存在很大错误(比如并不能钦定开头和结尾连边,这实际上对答案有影响,这花费了 10 min 发现;再比如,dp 的转移式是错的,这个花了 10 min 发现;同时第一次修改逻辑错误并没有修改完全,漏了一个初始判断,这个花了 5 min 发现)。

  4. 修改 dp 逻辑错误的过程中,犯下了 1 处笔误(这个最后调不动了,交给 AI 发现的)。

原因:

错误太多就不一一对应了。

总之大体是两种原因:想假了,笔误。

其中想假了是占据用时的非常大的因素,一方面发现想假了的问题需要耗费发现笔误将近两倍的时间,另一方面修改过程可能产生新的逻辑错误/笔误。

并且想假后进入二次编码和二次调试阶段后,人的专注力也会下降。

因此写代码最重要的是:想的是对的!其次是细节有想清楚。

G - Increasing Popcount

20 多min。框架 3 min,代码 10 min,调试将近 10 min。

问题:

  1. 框架写的太快了,因此真的就是起到了提纲的作用,而没有稍微想过内部要写什么,怎么写。

  2. 其中将 \([l,r]\) 拆成 \(O(log V)\) 个区间的过程中,具体比 \(l\) 大的部分怎么写,比 \(r\) 小的部分怎么写是比较混乱的,修改了 3 min。

  3. dp 的转移式是错误的,修改了 2 min。

  4. 发现了是 1-index,3 min。

  5. 发现了两处笔误中的一处(\(tpr\) 写成 \(r\)),只修改了一处居然过了,另一处还是会看视频发现的。

原因:

  1. 心态上比较急(最后一节课,想着再打一道代码),因此写框架的时候没有深入思考。

  2. 最开始想的时候没有想过代码实现环节,针对这种易错细节没有预先思考/写这个模块之前先进行思考。

  3. 还是想的问题中的式子问题。

  4. 没有静态调试意识,只是随机观看发现了笔误。

总结

至此可以尝试总结常见的编码时间长的原因:思考缺乏校验正确性,思考缺乏代码细节思考(复杂细节(比如数学式子等)的明晰和整体框架思考),前后逻辑混乱,笔误。

  1. 思考缺乏校验正确性:想出题目做法后,应该明确中间的推理转化过程(做到如果给别人讲述的话,起码逻辑自洽)以便于后续校验正确性。

  2. 思考缺乏代码细节:确认完正确性后,尝试从整体上思考代码要做的事情分成几个模块,每个模块需要实现什么功能,定义什么内容。同时对于自己没有自信实现清除的细节,应该在纸上明确写出(比如数学式子)或者实现代码重点关注。

  3. 前后逻辑混乱:编写框架(如果前面思考缺乏代码细节,那么应该在编写框架这一部分完成),如果迫不得已临时发现需要加入/前后对调/删除内容,应该停止编写代码,思考为什么需要这么做,这么做完对代码其它部分产生了什么影响,代码仍然正确吗?

  4. 笔误:编写代码需要专注,写完一部分代码稍微想一下对不对;写完一个模块就进行静态调试,比最后统一静态调试来得高效。

总结就是:正确了在写,写之前想清楚大框架和细节,加入意料之外的修改一定要三思,写代码一定要专注,并记得分段瞪眼。

展望

明天观察一下,实行这五项措施代码问题是否有改善。

希望尽快改善代码问题,以进入解决想题问题的阶段,这很重要,但是急不来。

目前模拟赛的瓶颈已经不是代码,而是想不出来。但是你不能代码问题还没解决就转向另一个问题。现在只能先把 trick 积累起来。

9.21

具体问题

A. 网格涂色

位置:noip t1。

重点分析对象。写了 40 min。

2 min 框架,15 min 编码。

编码过程有发现少了一处特判,采取之前提到的【停止编码,思考为什么需要这么做,这么做对其它部分造成了什么影响,这么真的正确吗】没有造成错误。

总共 5 min 发现两次笔误。(输出调试和静态调试)。

3 min 发现空间开小。

然后花费 5 min 发现题目数组含义读反,然后花费 10 min 发现 ub:

stk[++stktp] = (node){2,stktp?stk[stktp].l-1:n,i,b[i]};

在 c++14 及以下, stktp 的执行顺序是不固定的。

原因:

  1. 定义时没有考虑数组含义,即依据含义其理应承载的空间上限,随意地初始化空间大小。
  2. 读题目时只正确地概括了大意,但是没有明确每个变量/数组的含义以及输入格式。
  3. 完全没有 ub 的意识;ub 在本地调不出来。

解决:

  1. 统一定义所有全局变量。并在定义前考虑数组含代表的含义是什么,最多需要开多大。
  2. 编码前认真阅读输入格式,明确每个变量/数组的含义,以及数据范围。
  3. ub 其实遇到的概率很小,但是遇到基本上是致命的。自学 oi 开始,遇到的三次 ub 均是上述出现的类型,并都是往栈中推入一个元素时,同时在赋值语句右边使用了 tp 导致的。所以以后涉及到【往栈中推入元素】时,都需要 check 右边是否使用 tp。

B. 勇敢的 Bitaro 2

位置:noip t1偏难,可能s t2 比较合适。

总共 12 min。

4 min 框架,4 min 代码,3 min 发现逻辑错误:断环成链时没有赋值两倍。

问题:逻辑错误。

原因:

虽然最开始思考代码整体时有考虑到这点,但是具体编写框架时忘记了()

解决:

无论是否有认真思考代码整体或细节,编写框架都不能疏忽,应该思考这个部分实现什么功能,和前后配合的逻辑是什么,这么做真的正确吗?

C. 我的缆车

位置:noipt2适中。

总共 33 min。

其中 8 min 框架,13 min 代码。6 min调笔误(m 打成 n),5 min 调出空间开小(这个反复出现)。

问题:

没有进行分段静态调试,导致是全部打完代码,通过输出调试回溯定位到错误的,耗时较长。

解决:

分段静态调试。

P130116 树链剖分

位置:noipt3偏简单(?。

总共 40 min。

其中 9 min 思考,11 min 框架,17 min 代码,3 min 调整输入格式错误。

这个输入格式也和思考题目只是抽象题意,而忽略了具体细节有关系(反复出现)。

但是总体上这题逻辑较复杂,但是调试时间只有区区 3 min。我认为如果所有编码的调试时间占比都能做到这样,就很好。

和其它编码过程不同的是,这个代码鲜明的【思考代码整体和细节】和【框架】时间长,这是可以保留的成功经验。

总结

  1. 思考代码整体和细节前,加上阅读题目输入格式,明确数组/变量含义和数据范围这一步。

  2. 统一定义全局变量,考虑每个数组的含义是什么,根据含义最大需要开多少。

  3. 涉及栈时注意 ub 问题。

事实上笔误出现的频率较高,但是出现笔误的部分均没有经过静态分段调试。为什么不进行静态分段调试(因为潜意识觉得不会错,并且静态调试的意识又不强)。

因此可以总结出新的代码流程:

  1. 校验做法正确性。
  2. 阅读题目细节。
  3. 思考代码整体和细节。
  4. 框架(明确这个部分的功能是什么,它和前后的配合逻辑关系是怎样的,这样写真的对吗?)(特别注意定义全局变量部分空间开够!)。
  5. 编码(根据框架写完一段,瞪眼一段)(过程中加入意料之外的修改需要三思)。

感觉代码问题有在逐步改善,接下来准备把重心转向想题问题。

目前的想法是:

重点攻略和 noip t3/t4 难度相近的题目。

做一份题目 list,里面分成:待阅读题意,待深入思考,待阅读题解,待深入思考题解,待编写代码。这 5 个模块。

每天晚上完成阅读题意,空余时间深入思考(题解),然后电脑前阅读题解和编写代码。

然后找一些难度可能类似的题目放入list里(JOI 后三道(最后一道不一定做),mx/ht t3/t4)。当然模拟赛订正的优先级需要高一些。

晚上则将打过的题目,试图抽象模型,总结 trick。

目的是多积累一些模型,到时候能碰上。

但说实话我现在并不明晰想不出 noip t3/t4 的背后原因只是因为模型见得少吗?考虑后续观察一下模拟赛的过程,看看能不能发现问题。

9.22

发现之前晚上的时间全用来休息和总结代码了,没有剩下时间给看题和总结 trick,这是很坏的。

于是今天挑了一道难打的题,打算让晚上留下更多的时间。结果到晚上就忘记初衷了,还是没有把时间挤出来。只要明天晚上将中断的任务放到明天,应该会留下时间的。

具体问题

A. 室温

10 min。

B. 建筑工程 2

13 min。

这两题几乎没有调试时间。因此可以认为没有问题。

【数据删除】

这个题很难写,ai 写了大于 10 k,我写了 6 k 左右。

但是实际上,我的调试时间占比可能也达到了 \(\dfrac{1}{3}\)。

17 思考代码整体和细节,18 min 框架。

45 min 编码。

然后 15 min 第一次调试,补充了一个缺失的二分,修正了两个笔误 (f_fr 应该是 f_{fa_{fr}},以及 qy_i 应该是 qy_t)。

接着 10 min 调试出了一处逻辑错误:即树剖过程中 x 实际上被修改了,但是我编码过程中想使用初始含义的变量,仍然使用 x。

接着有一处重要逻辑错误,但是我随机调试了 20 min。最后无法,只好用暴力替换感觉可能出错的部分,然后发现:在链上二分时,逻辑上第一次不合法就可以 break,但是这样最后得到的 \(x\) 就不会和目标节点在一个重链上了,最后补的二分自然不合法。

分析

实际上代码方法中的前两个(正确性,想整体和细节)还是非常有效且高效的,可以保留;

然而面对代码量偏大、逻辑较复杂、整体耗时长的代码编写,似乎即使写了框架也不一定能完全避免逻辑错误,而静态调试解决笔误就更捉襟见肘了。

这背后的原因不是措施没有效果了,而是措施是一种保障,能提高正确率,但是最关键的因素是敲键盘时的脑袋是清楚的,能够想明白现在要做什么,这样对代码整体的影响是什么,这真的正确吗。然而经过较长时间的专注(思考整体和细节,以及框架),大脑已经疲惫,没有足够的精力支持我继续专注的编码。因此有些措施的效果就削弱了。

所以面对长时间的大码量题目,应该保持一个持续的、良好的编码节奏,一段时间专注后,就应该休息 3 到 5 min。

9.23

具体题目

A. 排列石子 2

B. 广告 2

两道热身题。每道在十几分钟内完成编码+调试。几乎没有问题。

CF2152G Query Jungle

位置:noip t2 偏难,t3 偏简单。几乎没有思维难度,主要是代码偏长一点点。所以放 t2 代码太长了。放 t3 又缺少思维难度。

40 min。5 min 思考,5 min 框架。20 min 代码。10 min 调试。

一处逻辑错误和两处笔误。

逻辑错误:

最开始填框架时,写到线段树 bd 部分。我的写法是对访问到的每个节点清空,然后叶子节点只需初始化部分被影响的变量。结果编码完成,重新静态调试时观察到这个地方时,我发现只要似乎不一定要清空节点,因为叶子节点初始化了,其它节点的是子节点merge来的。于是我删除了清空节点的代码,却忘记了叶子部分的代码是部分初始化,是建立在预先被清空的基础上的。花了 5 min 才重新发现错误。

这是二次修改没有三思导致的(这部分的功能是什么,为什么需要修改,这么修改后,和其它代码的会如何配合?)。

类似的还有一处笔误也是二次修改导致的:

树剖第二次 dfs 赋值链顶时:

初始错误:

nfd[dfn[x]] = ++dfsct;

最开始出现这个错误是因为打代码太心急导致的,然后再写完这个模块后的分段静态调试被发现,于是得到:

nfd[dfn[x]=++dfsct];

没错,就这么草率的改完就扔掉了。

正确代码。

nfd[dfn[x]=++dfsct] = x;

这已经不是二次修改后,只盯着局部没有考虑和前后代码配合的问题了,而是二次修改后局部就根本不对的问题。

另一处笔误不是很重要。

AT_agc005_d [AGC005D] ~K Perm Counting

位置:noip t2 适中。感觉代码和思路均无明显难点。

40 min。思考 5 min,框架 5 min。代码 10 min。10 min 调试,然后发现转移方程有误,5 min 想清楚,2 min 修改。

问题是想假了。即在验证题目正确性上有所欠缺。因为之前这道题目有和同学交流过,对于其中不暇思索提出的一些结论,同学没有予以反驳。所以现在再看这道题时,我就直接钦定其正确,正确性验证也是基于这个正确而展开的。

所以题目的正确性验证需要从头开始。

总结

今天的问题主要是:加入意料之外的修改没有停下来想一想;以及正确性验证。

今天代码时间偏长的可能因素:客观上被迫搬到楼下机房,所以时间较长。主观上思考整体和细节的时间变少了。

实际上比赛时我们会预设一道题目的编码+调试的完成时间区间。因此接下来也尝试开始做这件事:对一道题目尝试估计完成时间区间。尽可能去完成它(这时候就能考验综合的代码能力)。然后根据结果调整对自己代码能力的预估,进一步也可能更深刻地发现一些问题。

9.24

昨晚太迟睡了,今天训练状态不好,遇到一点困难就松懈了一整天。所以训练状态很重要。今晚开始调整作息,先从十一点前睡觉开始。

具体问题

CF2210F A Simple Problem

1h 10 min 左右。10 min 思考,5 min 框架。30 min 编码。

原因主要有:逻辑错误,二次修改,笔误。

逻辑错误:

对一个连续段计算赋值产生的贡献量时选择拔高前 \(\lfloor \frac{(len-p+1)}{2} \rfloor\) 个位置最优。但是我忘记 \(x\) 可能为负,造成最后拆完式子计算的结果错误,应该对 \(0\) 取 \(\max\)。

这是我在用暴力替换怀疑错误部分后,惊奇地发现答案仍然错误。然后先误以为是暴力写挂了,调了 10 min 左右,才发现是计算贡献函数出错。

解决:

明确暴力替换的逻辑是快速定位错误位置。并且暴力不容易写挂。

所以用暴力替换逻辑错误后仍然出错,更可能是其它相关部分出错,而不是暴力写挂。

二次修改:

写完代码才发现需要多测。于是临时修改,同时考虑其产生的影响做出修改(比如加入清空部分,但是漏掉了一个答案数组没有清空)。(开始调了一次,最后调了一次,总耗时 5 min 左右吧)。

这也和没有读清题意有关系。

笔误:

二处。其中比较重要的一处是,维护线段树区间带权加法时:

错误代码:

tr[x].mx+=d;

正确的代码:

tr[x].s+=tr[x].len*d;

注意到这一处其实有两个错误。然而第一次我只修改了其中一个。又花了一些时间才修改了另一个。总共快 10 min。

总结

注意到代码问题似乎开始转化,现在问题似乎从想不清楚,开始转化成写不清楚了。

现在发现一个有意思的现象。笔误:我面对同一处错误的多个错误时,往往是先发现其中一个。然后后面再逐步发现更多。二次调试:我写代码时开始有【二次修改会对其它部分有影响】的意识了,但并不完备,因此才出现漏清空的现象。

好像和第一次编码容易产出很多错误的本质原因一样,分段静态调试/二次修改耗时长的本质原因是:因为急着调出代码,因此发现错误后急着修改,缺少了必要的思考。实际上这个问题在昨天也有出现。

但是事实上,我昨天也总结处出了二次修改需要三思,但是今天写代码仍然没有做到。因为调代码的心态是急切的,我潜意识认为:调试代码不应该花费太多时间。所以调代码时才总是在赶。可以考虑为调试代码也预设时间,这样心才会静下来,才有“二次修改三思”的心理基础。

总结就是:二次修改/发现笔误修改需要三思。

9.25

P12948加训

预计 35 min。20 min 代码,15 min 调试。

实际上 20 min 直接通过。没有调试。这是好事。

D. 只是长领带 2

忘记预估时间了()

15 min 编码。没有调试。5 min 发现空间开小。

我分明有记得最后检查空间,但是我只是检查了每个数组对应的常量是否符合其含义。却没有检查常量的值是否符合其含义(比如 N 从 5e6 开成 5e5)。

需要将空间是否开够分成两个方面:常量字母是否初始化正确,数组是否选用了正确的、符合其含义的常量字母。

【HT-130-Div.2】核桃CSP-S组模拟赛

流程

355 pts。

思考时间:10 min 思考 t1。17 min 思考 t2。20 min 思考 t3。20 + 10 min先后思考 t4。

编码时间:10 min t1,10 min t2。

t3 做法是点分治+树状数组。21 min框架+编码,5 min 静态调试笔误,10 min 动态调试,发现一处逻辑错误,并在审慎思考后修改。10 min 卡常(通过注释发现瓶颈(居然是一只 log 的递归 dfs 而非2 只log的树状数组!),于是改成非递归),堪堪通过时限。

t4 再思考 10min,20 min 暴力,5 min 静态调试。15 min 换成线段树+树剖维护,7 min 调试。

策略问题

t4 我以为树剖+线段树 4 倍常数 \(O(n \log^2 n)\) 1s 能过 \(4 \times 10^5\)。实际上树剖居然能被卡满,同时没有标记永久化的区间加区间最值线段树(longlong)线段树常数比预想的大。

从事后的视角来看,感觉最后剩 30 min 选择去卡已经卡过、暂时没有明显优化的 t3 的常数。不如在明知 t4 有很大卡常空间的情况下,去卡 t4 常数。

但实际上赛时根据大样例的表现,t3 只比时限快 0.3 s,而 t4 远远快于时间限制。因此从当时来看,我在赛时做出的决策并没有明显不合理的地方。

因此以后所有题都要有优化做法/常数的意识,大样例不一定给满。我可能不应该根据经验评估一道数据结构题是否能够通过时限(因为我在这方面的经验确实不可靠,常常误判(比如 noi2026 day1 t2))。在比赛最后有空余时间的情况下,除了优先考虑检查前面题目代码的正确性(静态调试/拍子),接下来应该要根据可优化空间、和大样例表现来选择优化做法的题目(切忌对一道题目能够通过时限过分自信)。

P12962下棋

s t4。其中从线段树换成暴力的调试部分较长。错误原因有笔误,以及二次修改。

其中二次修改的错误主要是源自于修改对之前编写代码逻辑产生的影响:比如在不自知的情况下删除了二处需要保留到现在的代码。

感觉模拟赛打到最后还是急了。忘记了预设时间,写到最后也忘了分段静态调试。

但实际上,我最开始只是把优化当成单纯的二次修改,而忽略了这实际上相当于二次编码。所以应该按步骤重新开始思考。所以在开始的代码整体的思考也有缺陷。

总结就是:模拟赛要根据优化空间/大样例表现,进一步考虑优化题目。

感觉代码问题的本质应该还是没想清楚。比如这场 t3 代码的细节应该是远大于 t4 的。但是我 t3 调起来反而比 t4 舒服得多(t3 主要是在想怎么卡常上了)。写代码最重要的还是前两个步骤:验证正确性,思考代码整体和细节。剩下的步骤只是保证你的思想能顺畅落地罢了。

【思考代码整体和细节】的一个具体方法:思考时,有意识地思考自己最容易写错的地方是什么,重点思考这部分的细节。

【静态分段调试】的一个具体方法:局部构造样例模拟。

9.26,9.27

具体问题

S R4

表现:

思考:

t1 思考了 5 min,t2 思考了 20 min,t3 思考了 40 min,t4 粗略思考了25min。

然后会了 t1,t2,t3,以及 t4 20 pts 暴力。

然后粗略花了 10 min 安排代码的时间(框架,编码,调试等的最长时间),并且补充思考了四道题目的代码细节。

这时候还剩下 2 h 20 min。

然后 t1 10 min 完成。t2 框架+编码 10 min。然后花了 10 min 输出调试发现思路有问题。然后花了 10 min 发现是漏了一个大情况,在原有代码的基础上思考并验证了新做法的正确性,但是没有充分地思考代码整体和细节,20 min 打完新做法后,花了 20 min 左右才发现转移过程的笔误(p 打成 k)(这个笔误出现,首先是我细节没有想清楚,导致出现二次修改,并且二次修改后的静态调试对象不全(只检查了二次修改部分是正确的,而没有检查被影响到非修改部分代码))。

此时还剩下 1 h。

然后 t3 的框架+编码花费了 20 min。调一处逻辑错误花费了将近 40 min。

原因:

t2 代码的问题主要是:做法假、二次修改检查对象不全、调试。

做法假的原因是:由于时间紧,我只是猜测了一个结论,并没有通过思考验证正确性,而是手玩了题目给的小样例便猜测做法是正确的,实际上手完过程手完错了一个小样例,导致误认为做法正确。

二次修改检查对象不全:首先我只验证了正确性,而没有思考代码细节应该如何写。导致在写线段树节点合并的时候写到一半发现有疏漏,思考后进行二次修改,只验证了二次修改部分的正确性,但是没有验证这次修改可能涉及到的全部内容是否正确。

调试:当我 5 min 通过输出调试验证出错误大概率出在线段树部分时,我选择继续输出试图定位的更精准,而不是回头检查该部分是否有笔误,如果没有笔误是否有逻辑错误。

t3 的问题主要是:无效调试。

即我花费了 10 min 把错误定位到仅剩 4 行后,检查确认没有笔误之后,开始大量无意义输出,而不是转而去思考这部分是否有逻辑错误。

noip 10 R3

236 pts。t3 最后 10 min 想清楚哪里错误,没有调出来。

表现:

这场在【数据删除】建议下,尝试新的策略:将 t1,t2 分成一组,t3,t4 分成一组。对每组分别思考求解。

于是过程变成了这样:5 min 思考 t1,1h 5 min 思考 t2。

然后 5 min 完成 t1。

t2 5 min 框架,10 min 编码,然后花费 20 min 发现一处笔误,以及一处思想正确,但是代码实现本质错误的逻辑错误(即思想没有落实到代码上)。然后再花费 10 min 发现并修正了一个漏掉情况。

t3 想了 1h 10 min。

这时候还剩大概 1 h 20min。

5 min 框架,20 min 代码。接下来就是不断输出调试,然后对着大量的输出瞪眼,直到比赛结束。

原因

t2 和 t3 最重要的问题主要是两个:正确性验证不够彻底,调试效率低下。

并且这场模拟赛还有一个很重要的问题:缺乏合理的时间安排。即我没有为每道题目的思考、框架、编码、调试等预设时间,导致我心态上比较急。感觉这个策略的问题是,完全不看 t3,t4 会导致心里没底,同时缺乏思考,导致没有办法安排每道题目的时间。

所以接下来打算主体上还是沿用之前的策略,但是将前两道题目思考的优先级提高了。

总结

从模拟赛中观察到的问题是:模拟赛时题目思考大致做法的时间过长。正确性验证不够彻底。二次修改严重缺乏思考(正确性和整体、细节)。调试效率低下。

感觉二三的问题有共同之处,都是想不清楚。

所以接下来训练的方向应该是三个:如何在尽可能短的时间内想到大致思路。如何想清楚题目(包括第一次思考的正确性、代码整体和细节;以及二次修改时,能停下来思考)如何提高调试效率。

第一个的想法是:思考大致做法是思想要活跃,大胆思考各种方向和做法,避免因为害怕做法错误而过于谨慎,影响思考效率。等想到可能的做法后,再进行严谨的正确性验证。打算每天晚上找难度适中的两道题目,尝试集中思考,看看效果。

第二个的想法是:首先预设时间一定要做好,这样心态上才不会急。好像除此之外没有什么很好的方法了。打算找一些细节难想的题目每天一道,看看效果如何。

第三个的想法是:猜到代码大致错误在哪了,就应该停止输出调试,而转向检查笔误以及正确性。这个可以在日常的写题调试中观察是否解决。

9.29

接下来每天白天,从之前自己已经想过大致做法,但是因为代码原因而没有编写的题目清单中选取题目。然后固定时间,集中思考并完成这道题目。白天训练和晚上复盘时都需要注重自己心里在想什么。

其余训练如常。

9.30

今天的训练没有达成预计目标。本来预计至少完成【细节多的题目】训练,并且完成复盘的。但是花费了 3 个多 h 到现在,我才通过了【细节多的题目】。这个题目的总结要到明天了。

C. 马拉松比赛 2

本来当作热身题,但是被击败了。

这个题 1.5 h 完成。其中有 30 min 是在想新做法及其细节上的。

最重要的问题是:想假了。

最开始我观察到了一个结论:在一个节点取完球,就不可能再返回该节点,否则后面返回时再取不劣。所以一次取球,必然是取当前未取球的左端点/右端点。然后我错误地将【不可能返回该节点】的前提【在该节点取完球】想成了【在先前取过球】。进而我得出了结论:最开始从一个端点出发,然后一次性分别取一段前缀+后缀最优。

现在回头看,似乎当时我得出错误结论推理毫无逻辑可言。感觉解决措施是:给出一个猜想时,应该尽量严谨地给出该猜想的描述。这样自己才能明白自己确认正确的结论可以用在什么地方,才不会由正确的结论推出错误的东西。

9.31

具体问题

P17203 「奶龙OI」Round 1 - 数字约束

这是昨天完成的题目。代码 7k 多。

思考了 20 min。框架 10 min。代码 40 min,调试 40 min + 30 min。

其中思考和框架与预计时间一致,而代码比预计长了 10 min。调试比预计长了 30 min。

思考有以下几个点没有想清楚:

  1. 树剖+链上二分。

我们需要二分找到点 \(x\) 到根节点路径上的第一个黑点。

正常做法是不断跳重链,并在每条重链上寻找第一个 dfn 序小于 \(x\) 的点。

而我最开始写成了取每条重链上最浅的黑点,发现问题后,又改成找每条重链上最深的黑点。

  1. 线段树区间最小值ckmin。

线段树上需要对一个区间内,所有该区间最小值位置,对 \(v\) 取历史最小值。

我忽略了将区间 \([l,r]\) 拆成 \(O(\log n)\) 个区间后,不是每个子区间的最小值位置就是操作区间的最小值位置。

  1. 对两两直线求交点/判断半平面交是在左/右侧时,忽略了斜率不存在的情况(即一条竖线的情况)。

  2. 特判两条直线平行/直线不存在(直线不存在即 \(ax+bx+c \ge 0\) 中,\(a= b = 0\) 的情况)时,我将不等号强化成了等于。

调试:

发现调试到后面出现了定位不出错误的情况。导致了非常长时间的无效用时。

所以思考的问题是:

  1. 对于自己自信的部分(线段树、树剖等),缺少具体细节的思考。

  2. 缺少对思考细节正确性的验证。

调试问题:

  1. 在没有充分小的样例的情况,无法通过手玩样例定位错误在哪!

即我在没有被暗示错误可能出在哪的情况下,会不知道如何调试。

原因

针对思考,和调试给出原因。

思考:

  1. 拆解并选择重点模块。现在选择的题目的代码普遍复杂。所以先做拆解。首先将代码分成若干模块,从中找出最重要的若干模块(即是代码中实现的核心功能,而非依据自己感觉会不会错),分模块思考细节。这样只要对代码有清晰的认知,就不会出现漏掉细节思考的情况。

  2. 分模块验证正确性。对每个模块思考完具体细节后。尝试攻击这个模块(正向验证正确性,构造cornercase等)。

调试:

  1. 逐个模块排除错误。针对每个模块重新思考正确性(尝试局部构造样例模拟)/方便的话,直接用暴力替换该部分代码。

  2. 迫不得已的话对拍。

可以总结出新的做题流程:

  1. 思考大致做法,并验证正确性:提出一个猜想时,需要给出这个猜想形式化的描述,并予以证明;明确由一个新结论是由什么旧结论推导出来的,这部分的推导关系真的严格成立吗?

  2. 分模块思考细节,并验证正确性:将代码拆解成若干模块,选择其中的核心模块思考细节,并且分模块正向/举例验证细节的正确性,尤其需要考虑是否覆盖了所有情况。

  3. 框架+编码(过程分段静态调试)

  4. 调试:有小样例先动态调试;否则逐个模块排除错误(重新思考正确性/暴力替换);如果暴力好写且数据好造考虑对拍。

posted @ 2026-09-17 21:42  rabbit_mygo  阅读(146)  评论(0)    收藏  举报