OIer必备网站集:用角色分工和打开顺序替代链接囤积
我的浏览器书签里曾经塞过三百多个链接,分类文件夹从“题库”一直建到“待看”,结果那半年成绩纹丝不动,连省二都卡得死死的。后来我把整个书签栏清空重来,只留下每天真的会点的五六个入口,剩下的靠搜索和记忆去找,训练效率反而上了一个台阶。这件事让我意识到,对 OIer 来说,“必备网站集”真正的难点从来不是收集,而是在正确的时间打开正确的那一个。下面这份清单不谈“大全”,只谈角色分工和使用节奏:哪个入口承担输出、哪个承担反馈、哪个是查信息用的、哪个纯粹是工具。具体站点我用功能代号指代,一是避免文章变成半年就过期的导航页,二是角色不会变,站点会换,你完全可以把代号替换成你手头在用的那一个。刚入门的人照着建一套骨架,已经刷了一阵但感觉在原地转圈的人,可以对着后面几节的“打开顺序”和踩坑表做一次体检。
1. 收藏夹塞满三百个链接,成绩却卡在原地:资源囤积的三个副作用
先把一个反直觉的结论摆在前面:收藏这个动作本身会制造进步感。你点下收藏的那一秒,大脑已经给这件事发过一次奖励了,至于后面有没有真的打开过,那是另一回事。我带过的一个刚入门的同学,A 同学,曾经专门建了一个叫“OI 资源”的文件夹,里面按“题解、模板、课件、工具、博客”分了五层子目录,总数接近四百条,他跟我说“资料都备齐了,就差开始刷了”。三个月后我问他那个文件夹打开过几次,他说大概十几次,而且集中在最初两周。
1.1 第一个副作用:每次要做题,先花十分钟选平台
当入口太多的时候,“今天刷哪”这个决策的成本会高到离谱。人面对五个以上选项时,很容易滑向那个最不需要动脑的选项——刷题社区的最新帖子、看别人的复盘、翻竞赛闲聊区,一晚上就过去了。这类行为有个共同特征:它感觉像在学习,但它不产生任何提交记录。判断标准特别简单粗暴,一天结束的时候问自己一句:今天有几个 AC 是我自己写出来交上去的?如果答案是零,那么无论看了多少帖子,这天对竞赛能力的贡献都接近于零。
真正有效的做法是固定主力入口,只在主力入口出问题时才启用备用。这跟练字要固定一支笔是一个道理,换来换去的手感全是散的。我的建议是主力评测站只留一个,最多加一个备用,其余全部退到“遇到具体问题再搜索”的层面。你不需要记住所有的站,你只需要知道遇到哪类问题该去哪里找。
1.2 第二个副作用:虚假进度感会掩盖真实水平
囤积的资源会给人一种“我已经拥有它了”的错觉。课件下载了就等于学过了,模板抄进笔记就等于会写了,题单收藏了就等于刷完了。这种错觉最危险的地方在于,它让你在没有任何实际产出的情况下,把自我评估抬高了一个档次,于是你会去挑战明显超纲的题目,卡三个小时,然后崩溃,再回去继续囤资料,形成一个很典型的闭环。
我见过太多人卡在这个循环里。破局的方法是把“拥有”换成“产出”,每一份资料在打开之前先问:我要用它解决哪个具体问题?如果答不上来,就先别打开。带着问题去看资料和漫无目的地扫资料,吸收效率差着好几倍。
1.3 第三个副作用:你把时间花在了找“更好的资源”上
资源是有边际收益递减的。前十个小时里,换一个更好的题库能给你带来明显的提升;到了第一百个小时,你手里那个题库和“传说中更好的那个”之间的差距,早就小于你自己执行力的差距了。绝大多数人的瓶颈不在资源质量,而在同一道题被反复绊倒却没有总结。与其去找第五个题库,不如把上周 WA 的那三道题重新写一遍,不看题解,从零开始。
关于链接失效:具体站点会因为各种原因改版、迁移甚至关停,所以任何一份“网站集”都有保质期。我的处理方式是把清单写成“问题—去向”的形式,比如“需要一个能自助造数据的评测环境”,而不是写死一个地址。地址会变,需求不会。
2. 评测与题库:主力站怎么挑,备用站什么时候才配被打开
评测站和题库站是 OIer 的主战场,这部分选择错了,后面全是补救。我发现很多人选站的依据非常随意——看群里谁提得多就去哪个。这跟选教材只看封面差不多。挑主力站有一套相当硬的指标,按这套指标筛一遍,你会发现能同时满足的其实没几个。
2.1 主力评测站的四个硬指标
第一个是题面可读性。好的题面会把输入格式、输出格式、数据范围、样例解释写清楚,尤其是样例解释——很多含糊的题就是靠样例解释救回来的。题面写不好、样例只给一个、数据范围藏在正文中间的站点,会让你在“理解题意”这一步就消耗大量精力,而这一步本该是最省力的。
第二个是数据强度。这个只能靠踩。判断方法很实用:写一个明显的暴力或者带错误贪心的解法,交上去看看能不能过。如果一眼假的算法都能 AC,那这个站的数据基本是摆设,你在上面刷出来的“正确率”没有任何参考价值。真正有强度的评测会让你在边界数据上翻车,而且反馈的测试点信息足够你定位问题。
第三个是评测速度与反馈质量。这道题交上去,一分钟内出结果和十分钟才出结果,对训练节奏的影响是完全不同的。更重要的是反馈信息:只告诉你 WA 和告诉你第几个点 WA、期望什么、输出了什么,是两码事。后者能让你自己定位错误,前者只能让你去问别人。
第四个是题目是否成体系。散装题和体系题的区别在于,体系题往往有明确的难度梯度、专题分组、前置关系,你顺着刷就能形成知识网络。散装题刷一百道,脑子里还是一盘散沙。
| 指标 | 判断方式 | 不合格的后果 |
|---|---|---|
| 题面可读性 | 随便挑三道没见过的题,只看题面能否独立写出暴力 | 大量时间浪费在猜题意上 |
| 数据强度 | 用错误解法试探是否被卡 | 正确率虚高,赛场上原形毕露 |
| 反馈质量 | 故意写错,看反馈信息量 | 无法自查,只能依赖别人 |
| 体系性 | 看是否有专题/难度分组 | 知识碎片化,迁移能力差 |
2.2 题库按难度带分层,别在一个站里从入门刷到顶
我自己的题库使用方式是分层的,四个带,每个带用不同的站,切换的依据是当前阶段的瓶颈类型。
入门带的目标是“把语言和基本数据结构用熟”,这时候需要的是量大、反馈快、题面友好,不用挑站,能连续 AC 建立信心最重要。过渡带的目标是“见过足够多的经典模型”,这一层要选专题分组清晰的题库,一个专题一组,每组十几道,刷完这个专题就换下一个,不做无意义的重复。冲刺带的目标是“限时做出难题”,这一层必须用难度明显高于当前水平的题,而且要卡时间,做不出来就看题解,看完立刻重写。观察带不属于刷题,是定期去扫一眼更高层级的题目在考什么,保持对整体范围的感觉,别一头扎进细节里三年不出来。
这个分层最大的价值是防止你用错力。在入门带拼命刷难题,和已经到冲刺阶段还在刷入门题,是两种不同形式的浪费,而且都很难自我察觉。
2.3 备用站的正确用法:只在需要“第二意见”时打开
备用站不是主力站挂了才用的替补,而是有特定用途的。它最典型的两个用法:一是同一道题换个站重做,检验自己是真的理解了还是在记答案;二是在主力站的题面有歧义时,去别的地方看同一道题的描述,交叉验证一下。这两种用法都指向同一个功能——第二意见。
除此之外,备用站别碰。在备用站上漫无目的地翻题,和刷短视频的机制差不多,一直有新东西冒出来,但都不在你的训练路径上。我给自己定的规矩是:打开备用站之前,先在纸上写一句“我来这里是为了解决什么”,写不出来就不开。
3. 题解区与讨论区:这两个页面决定你是“看懂了”还是“会做了”
题解区和讨论区是最容易被滥用的两个地方,也是最容易出效果的。区别全在“打开之前”和“关掉之后”你做了什么。同一个人在同一个页面上的两种用法,效果能差十倍。
3.1 给自己定一条“允许点开题解”的硬规则
我给自己定的规则是这样的:一道题卡住之后,先不看题解,做三件事——写下当前的思路和它卡在哪一步、估计当前解法的复杂度、检查数据范围是不是漏了什么条件。这三件事做完,如果还是没有进展,再去看。看起来繁琐,但这一步的产出价值极高,因为你写下来的那个“卡点”就是你真实的知识缺口,带着它去看题解,你是有靶子的。
看题解的姿势也有讲究。第一,只看前三行或者第一个关键转折,然后合上,自己往下推。一口气看完的做法,你得到的是“哦原来是二分加树状数组”,但你自己推不出来的那一步依然不会,下次见面还是不会。第二,看完之后必须关掉所有参考资料,从零重写一遍,写到能一次 AC 为止。第三,重写完之后写一句总结:这道题的“钥匙”是什么?是某个模型识别,还是某个转化技巧,还是某个小结论?这一句话要能让你两周之后看到同类题时想起来。
一个很有效的自查:如果一道题你看完题解重写还是写不出来,说明你连题解里的技术细节都不熟,那问题不在题解,在这个技术本身。回去补基础,别在题解上打转。
3.2 讨论区真正值钱的是错误现象的检索
讨论区最大的价值不是“问”,而是“翻”。你遇到的绝大多数问题,别人都遇到过,尤其是那种莫名其妙的 WA 和 RE。检索的时候别只搜题名,那样往往搜出一堆无关的闲聊。有效的搜索词是错误类型加现象描述,比如“最后一个点 WA 但是本机对拍没问题”“本地能过提交就 RE”“数组开到上限就 MLE”。这些描述能精准命中那些踩过同一个坑的人。
翻帖子的时候注意看时间。技术类讨论里,五年前的帖子可能还在说一套现在已经被淘汰的写法,尤其是涉及语言标准、编译器行为的那些结论,一定要找近期的验证。看到一个方案先别急着抄,问自己一句:这个结论是在什么环境下得出的?
3.3 写题解是收益最高的复健手段
我状态下滑的时候从来不是靠“多刷几道”恢复的,而是靠写题解。具体做法是:挑一道最近做出来的、有难度的题,不看任何资料,从头把思路、证明、复杂度分析、实现细节完整写一遍。这个过程会暴露大量“我以为我会了但实际不会”的地方,尤其是复杂度分析和边界处理这两块,很多人做题时是凭感觉绕过去的,写的时候一较真就露馅了。
写题解还有个隐藏好处:它强迫你把代码从“能过”整理成“能看”。变量命名、模块划分、注释要不要加,这些在比赛时不重要,但在复习时决定了你三天之后还能不能一眼看懂自己写了什么。我现在翻自己两年前写的代码,注释齐全的那部分基本还能秒懂,没注释的那部分得重新推一遍,这就是差距。
4. 赛程、规则与赛后数据:信息差能吃掉你半个名次
这一节讲的是纯信息层面的事,跟算法能力无关,但每年都有人在这里栽跟头。而且这种损失特别冤枉——你完全有能力做得更好,只是因为你不知道。
4.1 赛历必须自己维护一份,别指望别人提醒
比赛信息分散在好几处,没有哪个单一入口能覆盖全部。我的做法是在手机日历里建一个独立分类,把所有确定时间的比赛全部录入,提前一周和前一天各设一个提醒。这不是为了防遗忘,而是为了倒排准备时间。一场比赛前一周该做什么、前三天该做什么、前一天该做什么,有了日历就能排出来。临时被告知“明天有比赛”的人,和提前一周规划的人,状态完全不在一个层面上。
另外一个容易被忽略的点是报名截止时间。有些比赛需要在系统里提前注册、提交信息、确认参赛,这些环节跟比赛时间不是同一个时间点,混在一起记特别容易漏。我在赛历里把“报名截止”也当成一个独立事件录进去,这个习惯救过我至少两次。
4.2 提交规范与评测环境的差异,值得专门开一个文档
不同评测环境之间的差异,比大多数人想象的大。编译选项、语言标准版本、递归深度限制、栈空间、输入输出格式要求、时间限制的宽严程度,每一样都能让本机跑得好好的代码在提交后出问题。我的做法是维护一个单页的对照表,把这些差异记下来,尤其是本地能过线上不过的那种,一定要记清楚原因,不要解决了就忘了。
| 常见差异点 | 典型表现 | 应对方式 |
|---|---|---|
| 语言标准版本 | 本地能用新语法,线上编译报错 | 提交前用低级标准编译一次自测 |
| 栈空间限制 | 本地深递归正常,线上直接崩 | 深递归改迭代,或提前评估深度 |
| 时间限制宽严 | 本机 0.1s,线上超时 | 按最严的估计,本地测时留足余量 |
| 输入输出格式 | 行末空格、换行数量不符 | 严格按题面样例构造输出 |
| 数据规模上限 | 相邻题目的上限差异巨大 | 每题单独确认数据范围,不要凭印象 |
这张表看起来平平无奇,但它是靠一次次翻车攒出来的。我建议你也建一张,只记录你自己踩过的,别人的经验你没踩过是记不住的。
4.3 赛后数据比比赛本身更有价值
比赛结束的那一刻,真正的学习刚开始。我赛后固定做三件事。第一,统计失分分布:哪几道是完全没有思路,哪几道有思路但写不出来,哪几道写出来了但没过。这三类的补法完全不同,第一类要补知识面,第二类要补实现熟练度,第三类要补细节和调试能力。第二,统计时间分配:这次在每道题上各花了多少时间,有没有出现“在一道题上耗了两小时结果只拿了部分分”的情况。第三,把没过的那几道题放进一个专门的清单,一周之内必须重做一遍,不看任何资料。
这套流程做下来大概要花掉比赛同等的时间,很多人不做,觉得浪费时间。实际上做和不做的差距,在一次两次上体现不出来,十次之后就非常明显了——因为不复盘的人每年都在犯同一类错误。
5. 在线工具与本地工具链:哪些该放浏览器,哪些必须落在硬盘上
工具类资源是清单里变动最频繁的一部分,在线工具今天能用明天可能就挂,本地脚本写一次能用三年。我的分工原则很简单:需要长期依赖、需要反复运行、需要在断网环境下使用的,一律本地化;一次性、探索性、需要可视化的,用在线工具。
5.1 对拍、造数据、复杂度估算分别该放在哪
对拍和造数据必须本地化,理由是这两件事的迭代频率极高,而且往往在比赛前的最后一段时间里用,那个时间点网络状况和在线工具可用性都不该成为变量。复杂度估算可以放在线上,因为它是一次性判断,用完就走,而且很多时候只是心里过一遍。静态检查这类东西,本地集成到编辑器里更合适,每次写代码自动跑一遍,能提前拦掉大量低级错误。
那些“可视化调试”“树结构画图”“状态转移过程演示”的在线工具非常好用,但只适合用来建立直觉,不适合当成训练依赖。我见过有人不看可视化演示就不会做区间类题目,这就是工具依赖,脱离了工具以后手推能力完全没有建立起来。
5.2 一份能直接抄的对拍脚本与造数据模板
下面这个脚本是最小可用的对拍框架,标程和待测程序各一份,随机生成数据,逐组比对,出问题就停下并保留现场。
#!/bin/bash # 编译三个程序 g++ -O2 -std=c++17 -o std std.cpp || exit 1 g++ -O2 -std=c++17 -o my my.cpp || exit 1 g++ -O2 -std=c++17 -o gen gen.cpp || exit 1 for i in $(seq 1 1000); do ./gen $i > in.txt # 用种子 i 保证可复现 ./std < in.txt > out.txt ./my < in.txt > ans.txt if ! cmp -s out.txt ans.txt; then echo "发现差异,第 $i 组,数据已保留在 in.txt" exit 0 fi done echo "1000 组全部通过"造数据程序尽量不要写死随机范围,用传入的种子作为随机源,这样同一个种子永远生成同一组数据,复盘的时候可以精确复现。
#include <bits/stdc++.h> using namespace std; int main(int argc, char** argv){ int seed = argc > 1 ? atoi(argv[1]) : 1; mt19937 rng(seed); int n = rng() % 10 + 1; printf("%d\n", n); for(int i = 1; i <= n; i++) printf("%d ", (int)(rng() % 20 + 1)); printf("\n"); return 0; }对拍的价值全在数据质量上。纯随机数据几乎测不出任何问题,因为绝大多数错误都发生在特殊结构上。我一般会准备三个生成器:纯随机、边界极端(全相同、递增、递减、最大规模)、带特殊结构的随机(比如树形结构取链、菊花、随机树三种形态)。三个生成器轮着跑,命中率会高很多。
5.3 工具误用的三种典型
第一种是只对拍了小数据。数据规模全是很小的值,你的程序在小范围里正确,一上大规模就溢出或者超时,对拍完全发现不了。我的习惯是把生成器的规模范围做成可配置的,小规模跑五百组,大规模跑一百组。
第二种是标程本身是错的。对拍的前提是标程绝对正确,如果你拿一个自己写的贪心当标程,那两边一起错的时候对拍会告诉你“全部通过”,这比不对拍还危险。标程要么用暴力,要么用经过验证的实现,宁可慢一点。
第三种是看到 WA 不看 TLE。对拍脚本只比对输出,等于完全放弃了性能验证。程序跑对了但是慢十倍,在赛场上一样是零分。所以除了对拍,还得有一组固定的大规模数据专门测时间,用time或者简单的计时打点看一下运行耗时。
6. 讲义、课件与模板库:把“看过”变成“推过”
这一类资源是最容易变成“收藏了就等于学了”的重灾区。讲义和课件的价值不在读完,在于跟着推一遍。我现在看任何讲义都遵守一条规则:看到公式和推导就停下来,拿纸自己推一遍,推不出来再看下一行。这个习惯让我的阅读速度慢了三四倍,但留存率提高的可能有十倍不止。
6.1 讲义的使用节奏:先做后看,带着问题看
最有效的顺序是反过来的——先做题,做不出来再看讲义。因为题目会给你一个明确的“卡点”,而讲义是线性展开的,你不知道哪一段是你要的,就会从头看到尾,看完什么也没记住。带着卡点去看,你的注意力会自动锁定相关的那几行,效率完全不同。
另一个细节是别把讲义当教材从头刷。讲义通常是某次专题讲解的整理,它的组织逻辑是讲者的思路,不一定是学习的最优路径。我一般只挑其中与当前卡点相关的部分,剩下的跳过去。完整精读一本讲义的时间成本极高,而且边际收益递减得非常快。
6.2 模板库要当代码维护,不是当摘抄本
模板库这件事,我的观点可能有点偏激:自己写不出来的模板,不要放进库。抄来的模板你不知道边界条件在哪、不知道适用范围的限制、不知道常数大不大,比赛时贴进去大概率是灾难。真正的模板库应该是你自己反复写过、优化过、验证过的那几十个东西。
维护上有几个具体要求。第一,每个模板单文件可独立编译,不要搞成一个大文件互相依赖,比赛时没法拆。第二,每个模板头部写清楚适用条件和复杂度,写清楚“什么情况下不能用”,这比写“它能做什么”更重要。第三,版本注释要有,改了什么地方、为什么改,三个月后的你会感谢现在的你。第四,定期清理,那些一年都没用过的模板要么删掉,要么重新写一遍再放回去,别留着一堆没验证过的代码占地方。
我用一个本地目录管理这些东西,配合版本管理工具记录改动。每次改动都写清楚原因,这个习惯让我在两次写错同一个边界条件之后彻底改掉了这个问题。
7. 把清单变成日程:我一周里的打开顺序
清单最终要落到时间表上,否则永远是清单。下面这套顺序是我实践了两年多稳定下来的,不需要严格遵守,但结构可以参考。
7.1 平日两小时:单线程,只做一件事
平日的两小时我分成两段。前一小时做题,从当前专题的题单里按顺序取,卡住超过二十分钟就记下卡点跳过,不纠缠。后一小时处理前面积累的卡点:看题解、重写、写总结。这个结构的关键是做和处理分开,做的过程中不查资料,查资料的时候不做题。混在一起的结果是两边都做不透。
平日我基本不打开讨论区和资料站,只开主力评测站和本地的笔记。原因很简单,两个小时本来就短,任何一个能让你分心的入口都值得关掉。
7.2 周末长时段:模拟赛加完整复盘
周末我会留一个完整的三到四小时做模拟赛,用真题,严格计时,中途不查任何东西。这个环节的最大价值不是练题,是练决策:什么时候放弃一道题、什么时候先拿部分分、时间怎么分配到三道题上。这些能力平时刷题练不出来,只有限时高压环境下才会暴露。
赛后复盘花的时间通常和比赛本身差不多。我把错题分三类记下来,一周之内重做没做出来的那几道。这个流程坚持下来,最明显的变化是同类错误的发生频率在下降。
7.3 一份踩坑速查表,贴在显示器旁边
最后把我自己踩过、见过、修过的坑整理成一张表,这部分是全文最没有技术含量、但实用性最高的内容。
| 现象 | 大概率原因 | 处理方向 |
|---|---|---|
| 本机能过,提交就错 | 语言标准或栈空间差异 | 用低级标准本地重编译,深递归改迭代 |
| 小数据全过,大数据出错 | 数据溢出或数组越界 | 检查变量类型上限与数组尺寸 |
| 对拍全部通过但考试挂 | 对拍数据太弱或标程有误 | 增加极端与结构性数据,标程换暴力 |
| 一直卡在一道题上 | 没有设定止损时间 | 提前定好二十分钟规则,卡住就跳 |
| 题解看完还是不会 | 缺少从零重写这一步 | 关掉资料,从空白文件重写一遍 |
| 状态越来越差 | 只有输入没有输出 | 减少看资料,增加提交和写总结 |
| 比赛时时间不够用 | 平时没有限时训练 | 每周固定一次完整计时模拟 |
这张表我建议你自己维护一份,只记自己真实踩过的,因为别人的坑你不会有痛感,记不住。我自己的那份从最初的七八条长到现在的三十几条,每一条背后都是一次实打实的翻车。翻到某一条的时候,脑子里能立刻浮现当时那个错误的现场,这种记忆强度是任何清单都给不了的。
说到底,网站集这种东西的价值不在“集”,在“用”。我现在的入口比三年前少了八成,提交记录多了好几倍,这个变化不是因为找到了什么神奇的站点,而是因为把“找资源”的时间全部换成了“写代码和写总结”的时间。真正稀缺的从来不是链接。