凌晨一点半,我盯着屏幕上那个红色的 Time Limit Exceeded,叹了口气。这已经是今晚第四次提交失败了。备赛日常大概就是这样:大部分时间你都在和超时、段错误、边界条件较劲,偶尔AC几个题能高兴半天,然后又得投入到下一轮刷题和复盘里。作为一个从零基础一路打到区域赛拿过奖牌的普通选手,我想把这些日常记录下来——不是鸡汤,而是实实在在的备赛方法、时间安排、踩过的坑和能直接用上的技巧。这篇内容适合正在为算法竞赛做准备的你,也适合刚入门的编程新手。不管你是想在比赛中拿奖,还是单纯想把编程功底打扎实,这套备赛逻辑都值得参考。接下来我按备赛从头到尾的顺序,把整个流程拆开讲。
1. 备赛前的目标定位与整体规划
1.1 先看清比赛规则,再决定怎么练
很多同学一上来就打开刷题网站埋头做题,觉得“只要题刷得够多,比赛自然就稳了”。这个想法不能说全错,但效率很低。备赛的第一步,其实不是刷题,而是搞清楚你要打的比赛到底是什么规则。同样是算法竞赛,规则不同,训练重点和赛场策略完全不同。
目前常见的竞赛形式大致分两类。一类是罚时制计分:每道题只有通过和不通过两种状态,通过题目数越多排名越靠前,如果通过题数相同,总罚时越少越靠前。每一道题第一次错误提交会计入罚时,有些比赛还会对错误提交有额外等待时间。这种赛制下,提交策略就很重要——不能看到有点思路就乱提交,必须在本地把细节验证好再交。另一类是分值赛制:根据通过的测试点数量或解题质量给分,即使没写出正解,暴力骗分也能拿到一部分分数。这种赛制下,拿到“部分分”的意识和能力比死磕正解更关键。
另一个需要看的是比赛时长和题量。比如一场三小时的个人赛,通常有6到8道题;一场五小时的组队赛,可能有10到13道题。你需要根据这个规格来估计自己的读题速度、写题速度和调试速度,再决定日常训练里一场模拟赛做多久、做几道题。
怎么获取这些信息?最靠谱的方法是找到官方发布的规则文档、往年题解和参赛者写的经验帖。把最近一两年的题目难度分布拉出来看一眼,你会很清楚大部分题目集中在哪些知识点上。我当时备赛前花了整整一个晚上做这件事,把每一年的题号、知识点、难度列了一张表,后续的训练计划基本就是照着这张表来的。与其瞎猜,不如先做几分钟的调研。
1.2 分阶段备赛计划:基础、强化、冲刺
准备工作做完之后,接下来要有一个可以执行的计划。我不推荐那种把每一天都排满的魔鬼计划,因为备赛少则几周多则半年,太紧的节奏很容易在中途崩掉。我更倾向于把备赛时间分成三个阶段:
基础期(1-2个月),目标是系统性地补完竞赛常用的基础算法和数据结构。这个阶段不要急着打模拟赛,而是按专题刷题,每个专题从入门题做到中等题,保证自己真的理解了原理,而不是背了代码。
强化期(1-2个月),目标是专项突破和模拟训练。这个阶段开始做较完整的模拟赛,也专门练习自己的薄弱环节。比如动态规划总是卡转移方程,那就连续一周、每天只做动态规划的题,直到看到一道题就能自动设计出状态。
冲刺期(最后3-4周),目标是保持手感、稳住状态。这时候不再学新知识,每天做一两道保持感觉的题,周末打一场标准时长的模拟赛,然后花大量时间复盘。赛前三天开始调整作息,让大脑进入比赛的节奏。
下面是一个我在基础期常用的周计划模板,你可以按自己的时间调整:
| 工作日 | 训练内容 | 时长 |
|---|---|---|
| 周一 | 算法专题A:概念学习+3道入门题 | 2小时 |
| 周二 | 算法专题A:2道中等题+整理模板 | 2小时 |
| 周三 | 算法专题B:概念学习+3道入门题 | 2小时 |
| 周四 | 算法专题B:2道中等题+整理模板 | 2小时 |
| 周五 | 本周错题回顾 + 薄弱点补漏 | 2小时 |
| 周六 | 半场模拟赛(2小时,4-5题) | 2小时 |
| 周日 | 休息或只看题解不动手 | 半天 |
注意这个模板里,每天的训练量并不夸张,关键在于“持续”。备赛是一场长跑,你不需要某天爆肝十小时,第二天又什么都不做。两周之后你会发现,哪怕每天只有两小时,积累也很可观。计划还要留出20%左右的弹性,因为学校里可能有课、有考试,硬生生往里塞任务只会让你很快放弃。
2. 核心技能训练:算法与数据结构怎么练
2.1 按专题刷题,而不是按难度乱刷
我刚开始刷题的时候特别容易犯一个错:打开题库,按通过率从低到高或者从高到低刷,今天做一道字符串,明天做一道图论,后天再碰一道数论。这样刷了半个月,题确实做了不少,但遇到新题还是没思路。问题出在知识没有形成体系。
竞赛算法其实是有固定几大模块的,基本可以分成:枚举模拟、二分与三分、前缀和与差分、贪心、动态规划、搜索(DFS/BFS与剪枝)、图论(最短路、最小生成树、拓扑排序)、数论(gcd、素数筛、快速幂、同余)、字符串(哈希、KMP、Trie)、数据结构(栈、队列、优先队列、并查集、线段树)等。
每个专题的正确打开方式是:先花半小时到一小时看懂基本概念和典型例题,然后做三到五道入门题,接着挑战两道中等题,最后把自己对这类题的套路总结成模板和笔记。做完一个专题再做下一个专题,知识点之间能够互相支撑,后面复习时也会轻松很多。
举个例子,动态规划可能是很多人最怕的专题。其实它的核心就三件事:状态怎么定义,转移方程怎么写,边界条件怎么初始化。我学背包问题时,就是反复问自己:当前这个物品,选还是不选?选了之后背包容量少了多少,价值加了多少?如果不选,那和前一个状态有什么区别?把这些用纸笔写清楚,再去写代码就顺了。很多同学动态规划学不会,是因为跳过了“手推状态”这一步,直接照着别人的代码敲,敲完也不知道为什么。
2.2 模板库:算法竞赛里的“作弊小抄”
这里说的“作弊小抄”不是说赛场上违规,而是指提前把自己的常用算法代码整理好,可以在比赛时快速调用。正规比赛通常都允许选手携带纸质模板进入赛场,有些比赛允许使用本地模板库,但就算规则不允许,整理模板的过程本身也是一种极好的复习。
不过要注意,模板千万不要直接从别人那里复制粘贴。必须要亲手敲一遍,加上自己的注释,理解里面每一行的作用。因为如果你不理解模板,赛场上遇到需要改参数或修边界的情况,你会无从下手。亲手整理的模板才是真正属于你的。
比如二分查找,看起来很简单,但边界条件特别容易出错。我早期用的版本经常在区间长度为2的时候死循环。后来我固定用一种“左闭右开”的写法:
// 在有序数组 a 中查找第一个 >= x 的位置 // 返回下标,如果不存在返回 n int lower_bound_pos(const vector<int>& a, int x) { int l = 0, r = a.size(); // 左闭右开 [l, r) while (l < r) { int mid = l + (r - l) / 2; // 防溢出 if (a[mid] < x) { l = mid + 1; } else { r = mid; } } return l; }为什么用左闭右开?因为这样mid在区间长度为2时,mid会取到左端点,配合l = mid + 1或r = mid一定能缩小区间,不会死循环。l + (r - l) / 2而不是(l + r) / 2,是为了防止两个大整数相加溢出。这些注释看起来琐碎,但到赛场上很可能就是救命的细节。
模板库怎么分类?我习惯按功能模块分文件夹,比如“基础算法”“数据结构”“图论”“数论”“字符串”。每个文件里除了代码,还有适用条件、复杂度和注意事项。例如在并查集模板里,我会写上“路径压缩+按秩合并,复杂度接近O(α(n)),不能用于删边场景”。这种提醒,只有自己踩过坑才写得出来。
2.3 学会估算复杂度,不做“暴力出奇迹”的莽夫
很多新手对复杂度没有概念,觉得能跑出正确答案就行。但竞赛题的数据范围会直接决定你该用什么算法。看到一个题,第一步应该根据数据范围反推期望复杂度,而不是立刻上手写代码。
举几个常见的对应关系。如果 n 不超过 10,那可以考虑状态压缩枚举,O(2^n) 或者 O(n!) 在 10的规模下没问题;如果 n 在 10^5 左右,O(n^2) 基本会超时,至少需要 O(n log n) 或者 O(n);如果 n 达到 10^9,那题目大概率考察的是数学推导、O(log n) 的二分或快速幂。养成每次读完题先写一句“n=10^5,需要O(n log n)”的习惯,会帮你省掉大量因选错算法而导致的重写时间。
除了复杂度,还要熟悉常用数据结构的基本操作复杂度。比如优先队列的push和pop是O(log n),排序是O(n log n),哈希表平均O(1)但最坏可能退化,并查集接近O(1)。这些知识点不需要背得很细,但要能形成直觉,毕竟赛场上的时间非常宝贵。
我还记得有一次模拟赛,我花了四十分钟写了一个暴力的排列枚举,然后发现数据范围是10^5,根本不可能通过。其实这道题就是一个贪心+排序,只需要O(n log n)。当时如果我先估算复杂度,就不会浪费时间。从那以后,我把“先看数据范围再想算法”写在了模板库的首页,每次训练前都会看一眼。
3. 日常训练的具体执行:一份典型备赛日的时间线
3.1 上午:理论补漏与模板维护
备赛日不一定非要从早上开始,但我会建议你把每天最清醒的两小时留给最难的内容。我自己的日程是早上先看算法书或者高质量题解,把最近做错的、没搞懂的知识点拿出来重新学一遍。这个环节不是“看小说”,而是要动手验证。比如看到一个关于线段树的写法,我会在编辑器里敲一遍,把每个区间递归的参数写清楚,然后思考pushup和pushdown究竟做了什么。
上午还适合更新模板库。每学完一个算法或者从题解里发现一种更简洁的写法,就把它整理进模板文件夹,同时删掉旧版本。整理的时候一定写上说明,比如这个版本有什么优势,使用场景是什么,注意事项是什么。以后冲刺期翻起来,你会感谢当时认真写注释的自己。
这里有一个小技巧:不要只整理“正解模板”,还可以整理“常见错误模板”。也就是说,把自己常犯的错写成一份清单,比如“数组下标从0还是从1开始”“开long long了吗”“多组样例是否重置了全局变量”。每次比赛前快速过一遍这些易错点,能帮你在实际比赛时少交几次罚时。
3.2 下午:模拟赛模拟的是状态,不只是题目
模拟赛是备赛日常里最核心的环节。但很多人把模拟赛做成了普通刷题——边写边查题解,中途还出去吃个饭,时间到了也不停。这其实是在自我欺骗。模拟赛的意义并不是练那几道题,而是练你在比赛压力下的时间分配、心态控制和手感。
我一般会这样安排:一场模拟赛严格按正式比赛时长来设闹钟,比如三小时,拿一套往年题目或者难度相近的题目,一开始就进入比赛状态。期间不看题解、不查资料、不接电话,尽量还原赛场环境。比赛的前十分钟只读题不写代码,把每道题的难度、题目类型、预估做法写在草稿纸上,然后挑一道自己最有把握的题先AC。
比赛过程中,如果一道题想了二十分钟还没有任何思路,我会立刻标记出来,先去做其他题,绝不在一棵树上吊死。每写完一道题,先检查数组越界、变量类型、输入输出格式,再提交。模拟赛结束后先别急着看排名,趁热打铁把没做出来的题补完,这个过程比比赛本身收获更大。补题时要认真读题解,弄清楚“我为什么没想到”,而不是仅仅把代码抄一遍。补完题之后,再用自己的话把整道题的思路讲一遍,只有能给别人讲明白,才说明真的会了。
我还建议每次模拟赛都记录一个简单的日志:哪道题花了多久、哪道题卡住了、卡的原因是什么、最终AC没有。坚持记录几周,你会看到自己的时间分配在慢慢变好,也能找出那些反复卡住你的题型。
3.3 晚上:复盘与错题本,让每天的题没白刷
晚上是我觉得最重要的一段时间,因为备赛中最容易忽略的环节就是复盘。很多人的训练量很大,刷了几百道题,但水平停滞不前,原因就是只做不复盘,错过的知识点没有进入长期记忆。
我的复盘流程可以简化成四步。第一步,重述题目:不看原题,用一两句话把题目要求和约束条件说出来。如果说不清楚,说明题目还没真正读懂。第二步,卡点分析:回忆自己在哪个环节卡住了,是读题漏了条件,还是某个知识点不熟,或者根本没想到要用某个算法。第三步,正解推导:对照题解,把正确做法从第一步到第十步完整地推一遍,并在关键位置停下来,问自己为什么这里要这样处理。第四步,总结套路:把这道题归到某个专题下,总结一句“以后遇到什么特征,就优先考虑什么算法”,写进错题本。
错题本不需要很精美,但要有结构。我会把它分成四个类型:读题失误、边界错误、复杂度选错、算法不熟。每周日把这周的错题全部过一遍,把连续出现两次以上的问题标红,那就是你下一周最需要弥补的地方。很多同学到冲刺期不知道该刷什么题,其实就是因为没有错题本可以参考。有了它,你的弱点一目了然。
4. 团队配合与资源整理
4.1 组队赛里,怎样分工才能不是“三个人一起卡一道题”
如果你参加的是组队赛,那么一个常见的问题是:三个人围着一道题讨论半天,结果其他题目都没时间做。这其实不是能力问题,是缺乏分工策略。
组队赛赛场上,读题结束后,三个人应该快速标记自己最有把握的题。比如A同学擅长图论,看到一道最短路题,立刻举手说“这题我来”;B同学擅长动态规划,于是包下背包和区间DP;C同学擅长字符串,就把KMP和哈希相关的题接走。每个人先去把能拿的题拿下来,而不是三个人盯着最难的那一道较劲。剩下的难题,等已有题目AC之后,再集合全队力量攻克。
两个人同时写同一道题的情况要尽量避免,除非是在交流思路阶段。如果一个人卡了很久,另一个人可以把题目拿过去看一眼,从不同的角度分析。平时训练时也要多打配合赛,固定好队内的命名习惯、提交顺序和沟通语言。比如约定“我说‘稳了’代表样例过了但感觉没错位,我说‘检查完了’代表准备提交”,这种细节能在紧张的时候减少无效沟通。
不过要提醒一点:分工不等于偏科。组队赛里你可以有所侧重,但每个人至少要会基础算法和中等难度题的操作,否则一旦队友负责的题型被出到了难题,全队的推进速度就会卡住。我见过不少队伍,因为三个人都只会做某一种题型,碰到变化题就集体沉默。因此,平时除了按强项分工,每周也要安排时间补一补自己的弱项。
4.2 共享知识库:把三个人的脑洞合成一个档案馆
组队备赛最有价值的东西,不是某一个人的模板,而是三个人共同维护的知识库。我们当时用在线文档建了一个共享库,分三块:模板库、题解库和易错清单。模板库里放每个队员整理核心算法,题解库里放每道模拟赛题目的思路和代码,易错清单里记录我们三个各自常犯的低级错误。
知识库的价值在于积累和复用。比如遇到一道题,你觉得解法不常见,把它的核心思路写进题解库,下次队友遇到同类题时可以直接参考。又比如你的队友在一道题上犯了一个关于取模运算符号的错,把这个教训写进易错清单,比赛前翻一遍,整个队伍都能避开这个坑。
在整理共享知识库时,我建议每个人都用自己的话写,不要直接复制网上的长篇解析。写得简短不要紧,但一定要是自己理解的产物。如果队友看不懂你写的东西,那说明你自己也没完全搞懂,需要再讨论一次。这个互相提问的过程,其实是效率最高的学习。
4.3 不只是代码,比赛环境也得提前适应
很多同学平时在自己电脑上写代码很顺,一到比赛就各种不适应,因为环境变了。所以赛前一两周,一定要抽几次模拟赛在正式比赛系统或相近的在线评测平台上进行,熟悉提交页面、错误反馈格式和编译器版本。
提前准备的东西包括:快速输入输出模板、常用代码片段、对拍脚本和打印版模板。所谓对拍,就是用暴力程序作为标准答案,和你的优化算法在随机数据上做对比,从而找出优化算法中的逻辑错误。我常用的对拍脚本是这样的:
# 对拍脚本:随机数据生成器 + 暴力程序 + 优化程序 #!/bin/bash for i in $(seq 1 100); do python3 gen_data.py > in.txt python3 brute_force.py < in.txt > out_brute.txt python3 optimized.py < in.txt > out_opt.txt if diff out_brute.txt out_opt.txt > /dev/null; then echo "Test $i: OK" else echo "Test $i: WA" break fi done对拍在调试阶段几乎是神器。当你用一个看似正确的算法一遍遍提交都WA时,写一个暴力版本去做对拍,往往几分钟就能找出错误样例。当然,暴力版本只有在数据范围小的时候才可用,它只用来验证逻辑,不是用来提交的。
比赛环境方面的小东西也别忘了:备好纸质模板、证件、水、一点补充能量的零食。赛场上很多队伍因为没吃早饭,到后半程脑子发懵。别把这当成小事,体力也是实力的一部分。
5. 常见问题与避坑指南:那些让我多交了N次罚时的教训
5.1 初期最容易犯的几个错
我接触过很多备赛初期的同学,发现大家的错误往往出奇地一致。首先就是刷题顺序乱,今天觉得二维数组题有意思就去刷,明天又去碰动态规划,完全不按体系来,最后每个专题都是半吊子。其次是卡题后缺乏思考时限,有人可能一道题想了五分钟就翻题解,看完觉得“原来这么简单”,但下次碰到变式还是不会。再有就是只刷题不复盘,做过两百道题,但问你“这周学了哪三个新套路”,一个都说不上来。
针对这几个问题,我的建议是:第一,给自己定一个明确的专题顺序,按顺序推进;第二,给自己设一个“思考时限”,比如简单题十五分钟没有思路可以看题解,中等题二十分钟,难题三十分钟,超时再看题解不丢人,但没有思考就看题解才是最大的浪费时间;第三,每AC一道题,就在错题本或刷题记录里写一句话,总结这道题的关键套路或易错点。哪怕只有一句话,积累起来就是你的“算法直觉素材库”。
5.2 赛前一周,应该做减法而不是加法
到了赛前最后一周,很多人会突然焦虑,觉得这个算法还没复习,那个模板还不够好,于是疯狂刷难题。我个人的经验恰恰相反,赛前一周的任务是做减法。
不要再学全新的算法,因为你既没有时间消化,也没有机会在比赛中灵活运用。与其学一个新东西导致头脑混乱,不如把已经会的内容练到滚瓜烂熟。每天做一到两道中等难度题保持手感即可,题目量不要大。重点应该放在三件事上:一是过一遍自己的模板库和易错清单,二是调整作息,把睡觉和起床时间调整到比赛当天需要的节奏,三是提前准备好比赛要带的物品,包括打印好的模板、证件、笔、水、能量补给。
赛前一天不建议再打完整模拟赛了。最多快速浏览几道题保持题感,剩下的时间用来放松,看看自己整理的笔记。紧张是正常的,但过度紧张会让大脑在赛场上短路。我每次赛前都会提醒自己:到了这个阶段,实力基本已经定型,比赛拼的就是把你的水平正常发挥出来。
5.3 现场比赛稳住节奏的三个实用技巧
赛场上的时间管理,和平时训练有很多不同。我总结出三个技巧,屡试不爽。
第一个技巧是前十分钟只读题。把所有的题都读过一遍,按难度和把握程度排序,心里有一个大概的计划。然后先做自己最有把握的题,保证第一道AC尽早出现。第一道AC不仅增加信心,还会让后面读题写码都更放松。
第二个技巧是卡题超过二十分钟立刻换。这个时间阈值可以根据比赛时长调整,但核心逻辑是不要在一个点死磕。换个角度,也许做完整道简单题再回来看,思路一下就通了。如果所有题都卡住,就回到最简单的暴力,先拿部分分或稳住某个题的提交,再逐步优化。
第三个技巧是提交前检查六项:有没有留下调试输出;数组大小是否够;变量类型是否需要long long;边界条件是否覆盖;多组数据是否清空了全局变量;输入输出格式是否符合题目。我见过太多人因为少了一个return 0或者大小写写错而白交一发罚时。比赛里每一发罚时都可能在最后排名中成为致命差距。把这份检查清单放在模板里,提交前对照一眼,成本极低,收益很大。
6. 心态调整与长期坚持
6.1 瓶颈期:刷了多少题都不涨分怎么办
备赛到一定阶段,几乎每个人都会遇到瓶颈期:模拟赛成绩总是波动在同一个区间,该会的题好像都会,但一比赛就排名上不去。这时候最忌讳的是加大强度、疯狂刷题。很多时候瓶颈不是“题刷得不够多”,而是知识体系有隐藏的缺口,或者是对某些套路的理解停留在表面。
我突破瓶颈的办法是“降维打击”:把刷题量减半,把原先刷过的中等难度题重新做一遍,但这次要求自己不看题解,尽量用多种做法完成,并且给同伴讲清楚每个做法为什么成立。这个过程会逼着你把“模糊的理解”变成“精确的理解”。另一个有效方法是参与讲解和讨论,给别人讲题的过程,会让你发现很多自己以为懂但其实经不起追问的地方。组队备赛的同学尤其适合这个方法。
瓶颈期还有一个常见原因是心态着急。成绩不动不代表没有进步,可能只是其他选手也在进步,排名自然难以提升。这时候把注意力从“排名”移回到“今天我有没有比昨天少犯一个错误”,状态反而会回来。我在冲刺期每周只关注错题本上标红的条目减少了多少,只要减少就是进步。
6.2 备赛是一场马拉松,身体和热情都要管好
这段备赛日记写到后面,我想说一件容易被忽略的事:身体状态和情绪状态。每次模拟赛一坐就是两三个小时,长期下来颈椎、眼睛、手腕都会抗议。我自己大二那年备赛最疯狂的时候,一天刷题超过十小时,结果肩颈疼到去医院,停了两天训练,状态反而更差。后来我养成了一个习惯:每刷题四十分钟就站起来活动五分钟,每天跳绳或慢跑二十分钟。别小看这二十分钟,它能让你的注意力和耐力明显变好。
情绪上,要学会接受失败和WA。备赛日常不可能每天都AC,更多的是被题按在地上摩擦。关键是被摩擦之后,能不能从每一道WA里学到一点东西。我把每一次WA当作一次“免费测试”,通过错误反馈告诉我哪里还没想清楚。只要这个反馈被利用起来,这次WA就是有价值的。
最后再分享一个小技巧。我每次比赛前,都会在纸上写一句话:“保持专注,先拿稳分,再追难题。”这个简单的提示能帮我快速回到自己的节奏。备赛的核心从来不是“我今天刷了多少题”,而是“我今天对算法的理解有没有更深一点”。把每天的时间花在真正能沉淀下来的积累上,比赛结果自然会慢慢显现。