跳至正文
首页 » 淮安信奥培训 » 信息学奥赛刷题3000道C++还是无缘省队?你忽略了“复盘”这个关键步骤

信息学奥赛刷题3000道C++还是无缘省队?你忽略了“复盘”这个关键步骤

一道简单题,两个世界

cpp

// 选手A的刷题代码
int main() {
    int problems_solved = 0;
    while (problems_solved < 3000) {
        solve_next_problem();    // 刷过去,从不回头看
        problems_solved++;
    }
    bool in_provincial_team = false;  // 三年过去,还是 false
    return 0;
}

// 选手B的刷题代码
int main() {
    int ability = 0;
    while (ability < PROVINCIAL_TEAM_LEVEL) {
        Problem p = select_problem();
        p.solve();
        p.deep_review();   // 每一题都榨干价值
        ability += p.gained_insight();
    }
    return 0;
}

如果你能看懂上面这段代码,你一定也在困惑:为什么有人刷题过千却止步不前,有人看似轻松却能直通省队?在信息学竞赛这条路上,最残酷的真相莫过于:刷题数量与能力提升之间,并不是线性关系,甚至可能毫无关系

当你在洛谷的提交记录突破3000大关,当你的代码量足以写一本小说,当近三年的真题集已被翻得卷边——省队名单上依然没有你的名字。这不是因为你不够努力,而是因为你忽略了竞赛进阶中最核心的引擎:复盘


一、刷题3000道的真相:当努力变成泡沫

在浙江、江苏、广东这些信奥强省,想要挤进省队,稳定攻克紫题(省选/NOI难度)是起码的门槛。紫题所考察的,已不再是基础知识的简单应用,而是网络流、后缀自动机、树套树等高级算法,以及背后深刻的数学模型和巧妙的转化思想

那么问题来了:为什么刷了3000道题,却连紫题的门都摸不到?让我们用C++的思维来审视那些“无效刷题”的代码模式。

陷阱1:无限循环在舒适区

cpp

while (able_to_solve_easy_problems) {
    solve_problem(difficulty = "熟悉题型");
    confidence++;
}
// 能力曲线早已收敛,却还在空转

很多选手沉迷于刷自己擅长的题型——擅长模拟题,就一口气刷几百道;熟悉线段树,就只找线段树的题。看着AC的绿色标记产生虚幻的成就感,殊不知这就像在代码里写了一个死循环:CPU满载,却没有任何实质输出

真正的高手会告诉你:做题的难度,应该选那些需要花费四十分钟到一小时才能做出来的题。太简单的是无效重复,太难的是无效挣扎。

陷阱2:只声明,不定义——眼高手低的“函数声明”

cpp

// 看到题解后
void solve_hard_problem();  // 只声明,不定义
int main() {
    solve_hard_problem();  // 链接错误:undefined reference
    return 0;
}

“这道题我看了一眼题解,思路懂了,就不写了。”——这是最危险的undefined reference。思维到代码之间隔着千山万水:边界条件、空间优化、常数因子、数据类型溢出……只有真正动手定义(实现)那个函数,编译器才会给你生成可执行文件

四川大学的李祉橙在CSP认证中就因为一个int没改成long long,查错花了一个小时。他事后反思:“真没想到大学的第一场测试就碰上‘爆int’这个老朋友”只看不写,等于只声明了能力,却永远无法链接出真正的实力

陷阱3:魔法数字与硬编码——思维固化的模板依赖

cpp

if (problem_type == "区间查询") {
    use_segment_tree();  // 不管数据范围,不管是否在线
}

看到区间查询就套线段树,看到最短路就写SPFA——这就是代码里的“魔法数字”,写死了思维。真正的优化是根据题目性质选择最合适的数据结构,甚至有时候暴力算法才是正解


二、什么是真正的“复盘”?——像调试代码一样解剖自己

在C++编程中,当程序输出错误,我们会做什么?调试(debug)。单步执行、查看变量、回溯调用栈,直到找到那个导致崩溃的指针。复盘,就是对你解题过程的调试。

2.1 复盘的调试三步骤

第一步:复现错误(Reproduce the bug)

模拟赛后,不要只看分数,立刻重现当时的思维过程。在脑海里或草稿纸上单步执行:

  • 读题时,哪些关键词被我忽略了?
  • 推导样例时,哪一步算错了?
  • 写代码时,是哪个变量让我陷入了死循环?

第二步:定位错误根源(Locate the root cause)

像用GDB打断点一样,在时间轴上找到“卡顿点”:

cpp

// BUG#01: 在第二题上耗时 1h 23min,实际应 30min 后放弃
// BUG#02: 对拍时发现大数据会 RE,数组开小了(N=1000 但实际输入 2000)
// BUG#03: 推导 DP 方程时漏掉了 k=0 的情况

第三步:修复并验证(Fix & Verify)

针对每个bug,写出“补丁”并验证

  • 下次遇到类似难度题,设定最大思考时间30分钟
  • 以后数组大小一律N+5,防止越界
  • 写DP前先枚举所有边界情况,用小数据验证

2.2 复盘的“代码审查”视角

顶尖OIer的复盘不止于调试,还会进行代码审查(Code Review)。对比自己的代码和大佬的标程:

  • 变量命名:我的ijk满天飞,标程用leftrightmid清晰明了
  • 模块化:我的main函数写了200行,标程拆成了init()solve()output()
  • 复杂度:我的代码虽然AC,但跑在超时的边缘;标程用了更优的算法,常数极小

这种审查会让你意识到:AC只是及格,优秀才是省队的入场券


三、高效复盘的三个层次:从“编译警告”到“架构重构”

就像C++代码的优化有不同级别(O0、O1、O2、O3),复盘也分三个递进的层次。

第一层:技术性复盘——消灭编译警告与运行时错误

这是最基础的-Wall -Wextra级别。目标是把所有“潜在风险”扼杀在摇篮里

  • 未初始化变量:比如int ans;直接累加,导致随机值
  • 数组越界for (int i=1; i<=n; i++) a[i]却开了a[100],n=101时就崩了
  • 整数溢出1e9 * 1e9直接赋值给int
  • 文件操作freopen的路径写错,导致爆0
  • 浮点精度if (sqrt(disc) == floor(sqrt(disc)))可能因精度失败

技术性复盘就是把这些warning变成error,强迫自己写出健壮的代码。在刷题中,对应的是:

  • 每次提交前,构造极端数据测试(最大值、最小值、全相同)
  • 对拍程序验证正确性
  • 检查空间是否足够,避免MLE

第二层:策略性复盘——优化时间分配与得分策略

这一层对应编译器的-O2优化,目标是让程序跑得更快、更省资源。更要紧的是,OI赛制有个特点:可以获得部分分。每道题有多个测试点,根据通过的测试点数量获得相应分数

在CSP/NOIP这种OI赛制的比赛中,策略往往比能力更重要

  • 难度误判:那道你花了1小时没做出来的题,实际难度级别是什么?如果重来一次,你应该在第几分钟果断跳过?
  • 暴力与正解的权衡:时间不够时,你是否写了暴力程序来拿部分分?很多时候,省队与一等奖的差距,往往就在于最后30分钟里,你是在死磕正解,还是在有条不紊地书写暴力程序骗分
  • 读题偏差:是否因为没模拟样例就写代码,导致全盘皆输?李祉橙在CSP认证中就因为读错题目,白写了一个树状数组优化的DP

CSP满分选手的建议是:“无法拿满分的题,可以考虑先拿80分,不必死磕于最后的20分”

第三层:思维性复盘——重构算法直觉与知识体系

这是-O3甚至手动汇编优化级别,触及算法竞赛的本质——思维建模

一题多解:这道题除了用线段树,能不能用树状数组?除了用后缀数组,能不能用二分+哈希?尝试用最朴素的方法(暴力)推导出最优解。

出题人视角:如果你是出题人,你会设置什么数据卡掉我的程序?这题的核心考点是什么?(是数学推导,还是代码实现?)

知识融合:将新学的算法与旧知识建立连接。比如学了“线段树合并”,回去看“并查集”和“可并堆”,思考它们的异同。

这一层复盘会让你逐渐形成“算法直觉”——看到题目关键词就能映射出可能的解法路径,就像老手看代码一眼就能嗅到性能瓶颈


四、如何将复盘融入日常训练——像管理代码一样管理学习

4.1 建立你的“算法仓库”与“Bug追踪系统”

用Markdown或笔记软件维护两个仓库

  • 算法仓库:按专题分类,记录每种算法的核心思想适用条件模板代码易错点
  • Bug追踪系统:每次复盘发现的思维bug都记下来,打上标签([边界条件][复杂度误判][读题偏差])。每月review一次,看看高频错误是什么

4.2 为每次模拟赛写一份“调试报告”

模拟赛后,强制自己花30分钟写一份调试报告

cpp

/**
 * 模拟赛:2025.03.15 洛谷月赛
 * 成绩:100+0+30=130
 * 
 * 主要问题:
 * 1. T2 爆零原因:数组开小 (RE) + 忘记开 long long (WA)
 * 2. T3 只拿部分分:只写了暴力,没时间想正解
 * 
 * 改进措施:
 * - 以后数组大小 = N+10,所有变量默认 long long
 * - 前 30 分钟快速浏览所有题,先写暴力再攻正解
 */

4.3 坚持“补题”习惯

打完一场比赛后,一定要补题——继续做你没做出来的题。建议每次比赛在你没做出来的题中,选最简单的一两道题做会。实在不会就去看题解,但看完题解后,写一篇简短的题解,目的是为了让你把这道题再想一遍

4.4 重视比赛中的“错误记录”

CSP认证允许将纸质材料带入考场。建议把训练中常犯的错误记录下来,考试中时不时看一下,提醒自己不要犯错。对于一些不太熟悉的算法与定理,也可以打印带入考场辅助解题


五、结语:代码质量比数量更重要

cpp

// 错误的刷题观
for (int i=1; i<=3000; i++) {
    solve_problem();
    if (!review) continue;  // 永远不复盘
    // 能力提升?不存在的
}

// 正确的刷题观
while (true) {
    solve_problem();
    deep_review();  // 每次迭代都优化
    ability += insight_gained;
    if (ability >= PROVINCIAL_TEAM_LEVEL) break;
}

在C++的世界里,一段优雅、健壮、高效的代码,往往不是一蹴而就的,而是经过无数次调试、重构、优化才诞生的。你的能力也是如此。3000道题只是原材料,复盘才是那个将原材料锻造成利器的熔炉

高效的学习比麻木地堆叠学习时间有效。在学习过程中,如果能抓住自己的弱项并加以训练,通常能得到更好的效果。代码能力较弱可以多找模拟题训练;思维与算法较弱而代码能力较强,则可以寻找对应的题目将思路思考清楚,而不一定要将代码写下来,从而节约时间并使训练更有针对性

下次当你准备开启新一轮题海战术时,不妨先问自己:“我是想提交3000次WA,还是通过一次深度复盘,让下一次提交直接AC?”

从“Accepted”到“Provincial Team”,中间隔着的,正是那行被你遗忘的代码:

cpp

if (!review) continue;  // 请务必把这行删掉

参考文献

  1. 知乎专栏. 信奥强省想进省队,起码需要搞定紫题
  2. 哔哩哔哩. 5.9 集训终结复盘:从统计建模到 CSP-J真题全拆解
  3. 中国计算机学会. CSP满分说 | 四川大学李祉橙
  4. 小码王. 信息学竞赛三大赛制深度解析
  5. 慧明科技. 信奥赛冲刺的最快方式,没有之一
  6. 博客园. OI 学习指