☰
在线判题系统OJ刷题:从19题到21题,避开WA/PE/TLE全攻略
2026/10/3 3:01:09 网站建设 项目流程

如果说刷OJ(Online Judge,在线判题系统)最容易让人心态崩溃的节点,我投票给第19题。前十几题基本都是"a+b"这类热身题,提交上去闭眼AC,自信心膨胀得不行。但刷到第19到21题这个区间,题目开始变着花样折腾你:多组输入、字符串边界、数学特判,稍不留神就是一个Wrong Answer。更气人的是,本地编译器跑得好好的,一交上去就错,改来改去也找不到原因。这篇文章就是围绕这个阶段写的,我想拆一拆OJ刷题从第19题到第21题这段普遍遇到的坎儿,讲讲判题机制到底怎么回事、常见报错怎么排查,顺便聊聊我试过的几个OJ平台(HDU OJ、郑州轻工业大学OJ、东方博宜OJ这类)的差异。不管你是刚开始刷题的大学生,还是准备找实习想刷点算法基础的,只要卡在这个区间,这篇文章应该能帮你少走不少弯路。

这个阶段的人有一个共同特点:已经不是完全不会写代码的新手,但又没过"会做题"的坎。说白了,代码语法都认识,但一碰到OJ的判题规则就懵。别急,下面我按实际刷题的顺序,从原理到实操一步步讲。

1. 从"19-21题"说起:入门阶段的真实处境

1.1 为什么卡在19题的人最多

我当年刷HDU OJ时,前18题几乎全是送分题,很多就是求个和、比个大小、算个阶乘,题目描述里连拐弯都不打。但到了19题往后,题目难度曲线突然陡了一下,开始出现字符串处理、多组数据输入、格式要求严格的输出。很多人在这个区间第一次遇到Presentation Error(PE),也是第一次知道原来"输出结果对"和"输出格式对"是两回事。

这类题目之所以容易劝退,不是因为算法有多难,而是因为考点从"你会不会写代码"变成了"你能不能完整地理解题目规则"。第19到21题恰好是这种转变的典型代表。我自己带过的几个学弟学妹,几乎都在这个区间发出过灵魂拷问:"我明明本地运行样例完全一样,凭什么判我错?"这种挫败感很容易让人放弃。

把这个区间单独拿出来讲,就是想说明:你在这里卡住,不代表你智商不够,只是你还没习惯OJ的规则。一旦适应了,后面再刷几十道题反而会轻松很多。

1.2 这个阶段最容易出现的三个错觉

第一个错觉是"运行结果对就是对的"。很多人写代码只看样例输出,样例过了就提交,结果WA(Wrong Answer)得莫名其妙。OJ判题比对的是你程序在任意合法输入下的完整输出,不只是题目给你看的那个样例。样例只是给你一个参考,它覆盖不了所有边界情况。

第二个错觉是"本地跑通就万事大吉"。本地编译器不会因为你数组开小了就报错,也不会因为你少初始化一个变量就警告。很多时候代码在本地能跑,只是因为内存里的脏数据碰巧没出问题,换到OJ的评测环境就露馅了。我见过一个同学在本地跑第19题,10组数据全部正确,一交上去TLE,原因是他数组开成了100000,每次循环都从1遍历到100000,实际题目数据范围只有1000,白白超时。

第三个错觉是"不会做就是自己太菜"。说实话,能卡住人的往往不是算法本身,而是没读懂题意。比如题目说"输入以0结束",你可千万别把0当成有效数据处理;题目说"每个结果后面跟一个空行",那就不是空格而是空行。这些细节在题目描述里写得清清楚楚,但新手习惯快速扫一眼就开写,自然容易翻车。

2. 在线判题系统是怎么"判"的:机制决定策略

2.1 从提交到AC,OJ到底做了什么

想搞明白为什么老错,最好先搞清楚OJ的完整工作流程。你点下"提交"按钮后,评测系统会做四件事:编译你的代码、用预设的测试数据运行你的程序、捕获程序输出、和标准答案做比对。比对时绝大多数题目是逐字符精确匹配,少数字符串或几何类题目会启用Special Judge,只验证你的输出是否在合理范围内。

所以OJ的判定结果其实就是在告诉你,你这个程序在哪个环节出了问题。汇总一下常见结果:

判定结果含义最常见的诱因
ACAccepted,通过恭喜,不用改了
WAWrong Answer,答案错误算法思路不对、边界值没处理、初始化遗漏
PEPresentation Error,格式错误多了空格、少了空行、输出末尾多了东西
TLETime Limit Exceeded,超时算法太慢、死循环、用了过慢的输入方式
MLEMemory Limit Exceeded,超内存数组开太大、递归层数过多
RERuntime Error,运行时错误数组越界、除零、野指针
CECompile Error,编译错误语法问题、选错语言标准

我个人觉得,新手最需要理解的是AC和WA之间的差距。OJ里面很多题目是"一题多解",只要输出正确就行,不需要关心你用的方法是不是和答案一样。但前提是:你必须在题目规定的时间限制和内存限制内跑完。也就是说,OJ不光考你会不会,还考你写得够不够快。

2.2 输入输出格式:新手翻车重灾区

OJ题目和平时写单次运行的练习程序最大的区别,就是输入输出格式。大多数题目都支持多组测试数据,你的程序必须能循环读入,直到输入结束。如果用C语言,最常见的就是这样:

#include <stdio.h> int main() { int a, b; while (scanf("%d %d", &a, &b) != EOF) { printf("%d\n", a + b); } return 0; }

这里scanf的返回值是成功读入的参数个数。读到文件末尾时,它返回EOF,循环就终止。如果你把while条件写成while (scanf("%d %d", &a, &b)),在某些评测环境下,最后一次读取失败时返回值可能是0,循环会提前结束,导致漏掉最后一组数据。这个问题在OJ上很经典,我见过不少人在第20题这种"计算并输出"的题目上,因为这里写错丢掉AC。

如果是Java,可以用while (in.hasNextInt())作为循环条件;Python则是for line in sys.stdin。原理都一样:读到流结束就停。

输出格式的坑就更多了。常见的规则有:每一行输出后要换行、多组结果之间要不要空行、行首行尾不能有多余空格。题目里如果写了"每个测试用例的输出占一行"或"样例输出"长什么样,你就照着那个格式来,多一个空格都可能变成PE。PE虽然不算错,但也不会给AC,必须重新提交。

3. 三道代表性入门题的全过程拆解

虽然不同平台上"第19题、第20题、第21题"的具体题目可能不一样,但这类题目通常非常有代表性。我以最常见的三道题为例,走一遍完整解题流程,你看完就明白这个阶段该怎么思考了。

3.1 第19题:字符串处理的边界

这类题目常见的形式是:输入一行字符串,要求统计单词个数、反转字符串或者判断回文。比如郑州轻工业大学OJ和杭电OJ的早期题目里,都有一堆字符串题。它们共同的坑点有两个:一是字符串怎么读才能读进空格,二是数组开多大才够。

先看读入。如果你用C的scanf("%s"),遇到空格就会停,根本读不了句子。要读一整行,C里建议用fgets:

#include <stdio.h> #include <string.h> int main() { char s[1005]; while (fgets(s, sizeof(s), stdin) != NULL) { // 去末尾换行符 int len = strlen(s); if (s[len - 1] == '\n') s[len - 1] = '\0'; printf("%s\n", s); } return 0; }

这里有个细节:fgets会把换行符一起读进来,所以统计长度或输出时要注意。如果用C++,cin.getline也有类似情况。Python则可以直接用strip()去掉换行。

再说边界。字符串题最容易错的就是数组越界。很多题目字符串长度上限是1000,但你在循环里写for (int i = 0; i <= strlen(s); i++),多出来的那个等于号就可能访问到结尾的空字符,导致RE或逻辑混乱。我的习惯是数组长度在上限基础上加10,遍历时明确用< len,而不是用<=。

3.2 第20题:数学模拟的坑

第20题最常见的类型是素数、最大公约数、进制转换、数列求和这几种。我拿素数判断举例,因为这个题看起来简单,实际错的人一堆。

int is_prime(int n) { if (n <= 1) return 0; for (int i = 2; i * i <= n; i++) { if (n % i == 0) return 0; } return 1; }

第一坑:1不是素数也不是合数,n <= 1一定要返回假。第二坑:循环边界是i * i <= n,但这里的i最好用long long,否则当n接近int上限时,i * i会溢出。第三坑:2是素数,上面这个函数里2能正确返回真,但有些写法是for (int i = 2; i < n; i++),遇到n=2时循环不执行,直接返回1,这没问题;可就怕有人初始值写成i = 1,那所有数都会被1 % 1 == 0坑掉。

数学题还有一个特点:数据范围往往会逼你选对方法。比如让你判断1000000以内所有素数,你要是每个数都从2除到sqrt(n),勉强也能过;可要是让你判断100000000以内,那个复杂度就危险了,得考虑用筛法。这就是为什么刷OJ不能只看代码能不能跑,还得估算时间。一般评测限时1秒,代码的循环次数控制在千万级别以内比较稳妥,超过一亿基本就TLE了。

3.3 第21题:多组数据与提前终止

第21题常以"输入以0结束"或者"第一行是N,接下来是N组数据"的形式出现。前者是典型的"哨兵值"控制循环,后者是"先读个数再循环"。

先看以0结束的写法:

#include <stdio.h> int main() { int n; while (scanf("%d", &n) != EOF && n != 0) { // 处理 n } return 0; }

注意顺序:scanf必须成功读入后再判断是否为0,所以n != 0放在第二个条件。如果写成while (n != 0 && scanf(...)),n在第一次判断时还没被赋值,行为未定义。这种细节在OJ上是真实发生过的,别问我怎么知道的。

第一行先读N的写法更简单,但有个容易忽略的点:N后面往往跟着N行数据,如果你在前面用了scanf("%d")读N,接下来第一行数据的换行符还在缓冲区里。此时如果你用getchar或gets读字符串,可能会读到空串。解决办法是用scanf连续读,或者读数据时跳过换行符。

这个阶段练的就是这种"输入输出流程控制"的熟练度。说白了,OJ里很多题目的算法可能只需要十行代码,但前面的数据读入能把你绕晕半小时。

4. 刷题报错排查:从WA到AC的完整链路

4.1 WA案例复盘:"我明明本地是好的"

我总结过自己WA的几百次经历,大部分都能归到三句话里:变量类型不对、初始位置不对、特殊情况没处理。

类型不对最常见的是int溢出。比如求1到n的和,n是100000,和是5000050000,int根本装不下,必须用long long。OJ里的数据范围经常是这样卡着你的,你不对着题目描述看,光用int写,样例小数据能过,大数据就WA。

初始化位置不对,多发生在多组数据的题目里。很多人在主函数开头定义sum = 0,但放进while循环之后忘了每次重新赋值。结果第二组数据一进来,sum还带着上一组残留的数值,答案自然错。这属于最冤的一种WA。

特殊情况没处理,就是边界值。比如排序题里所有数相等怎么办,再比如"输出前导零"的数位题,9999和1000这种临界数。我一直以来的习惯是把样例改出几个变体自己测:最大值、最小值、单元素、重复元素、空串。这套测试样例跑下来,能过滤掉大部分WA风险。

4.2 TLE与MLE:效率是练出来的

如果你刷题遇到TLE,先别急着怀疑评测机卡,大概率是你代码本身的复杂度超了。怎么估算?看循环。如果你写了两层循环,每层都跑到十万,那总操作数就是百亿级别,1秒内绝对跑不完,必须换思路。

我常用的优化手段有三个:

  • 把cin/cout换成scanf/printf,或者加上ios::sync_with_stdio(false);加速
  • 把重复计算提到循环外,比如循环内部不必要的函数调用、重复的strlen调用
  • 换算法,比如暴力枚举改成双指针、前缀和、二分查找

MLE相对少见,但一旦出现,多半是数组开得太任性。有些题明明只用到100个数据,非开成1000000的全局数组,内存直接爆掉。还有递归深度过大的问题,深度到几十万层时栈会溢出,表现为RE而不是MLE,但本质都是内存问题。

4.3 PE:OJ特有的"洁癖"

PE是OJ特有的结果,它的意思是:你的答案内容全对,但格式不对。常见原因有:行尾多了一个空格、每行之间多了一个空行、少打了一个换行、输出的英文字母大小写和题目要求不一致。

遇到PE,解决的思路很简单:把题目给的输出样例下载下来,用diff命令或者文本编辑器的对比功能,逐字节对比你的输出和标准输出。很多新手不知道这个操作,所以我在这里多说一句:在本地用文件重定向跑程序,生成out.txt,再和题目提供的sample_out.txt做比对,任何多出来的空格、空行都能一眼看出来。

提示:写OJ题的时候,输出末尾的换行通常可有可无,但行与行之间的空行必须严格按题目要求。如果题目说"每个样例后面输出一个空行",那就需要在每组结果后额外printf("\n")。

5. 不同OJ平台怎么选:几家平台实测体验

5.1 常见OJ的特点对比

搜索热词里出现了很多OJ平台:杭电OJ、郑州轻工业大学OJ、东方博宜OJ、杭师大OJ、华为OJ等。这些平台我大多注册过,简单聊聊使用感受,方便你选一个适合自己的。

平台适合人群特点注意事项
杭电OJ(HDU OJ)算法竞赛入门到进阶题目全、经典题多、讨论区资料丰富全英文题面,对英语有一定要求
郑州轻工业大学OJ大一新生、校内课程中文题面多,和课程贴合紧密部分题目需要校内账号,游客可刷公开题
东方博宜OJ编程初学者、中小学生中文、题目友好、题型分类清楚难度梯度较缓,适合打基础
杭师大OJ校内教学、课程实验题目和作业绑定,有班级功能外校用户可能无法访问部分题目
华为OJ求职笔试准备题目更贴近工程场景、字符串处理多部分平台已改名为华为OD机试,题面较长

我的建议是:如果你刚开始接触OJ,先找中文题面、难度梯度合理的平台,比如东方博宜或者郑州轻工业大学OJ,把输入输出这个基本功练熟。等你能稳定刷完50道入门题了,再切到杭电OJ刷HDU 1000到1100这个区间,感受一下经典竞赛题的风格。华为OJ这种偏求职风格的,可以放到准备面试前再专门刷,里面的题更综合,不太适合纯新手。

5.2 刷题路线:从19-21题往后怎么走

刷到第21题,说明你已经过了"连输入都不会写"的时期。接下来我不建议继续按题号硬刷,而是按主题刷。大致顺序是:

  1. 简单模拟和枚举(10道)
  2. 字符串处理(10道)
  3. 排序和查找(10道)
  4. 贪心算法(10道)
  5. 搜索(DFS/BFS)(10道)
  6. 动态规划入门(10道)

每一个主题刷的时候,把AC的代码留档,同时写一句话笔记,记录这道题的坑在哪里。这条笔记比代码本身值钱得多。因为代码是固定的,你的思维过程才是下次遇到同类问题的解题钥匙。

我还想多说一个习惯:不要死磕一道题超过一小时。如果你在一个题上卡了超过一个小时,说明方向可能有问题,这时候去讨论区看看别人怎么描述思路,比继续钻牛角尖强。OJ的讨论区不是抄答案的地方,是帮你打破思维定式的地方。

我个人刷题的时候还有一个习惯:每道题AC之后,会把我的代码拿到本地去跑一些极端数据,比如最大值、最小值、随机值,确认它不是"侥幸AC"。因为OJ的测试数据往往覆盖不到所有情况,可能你靠运气过了,但换个数据就错。这个习惯帮我在后面的比赛里少翻车很多次。这个方法也推荐给你,从第19题开始养成,后面会越来越值钱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询