1. DHUOJ 基础题 118/119/120 到底在考什么1.1 先搞懂 OJ 到底是个啥如果你刚接触编程、刚开始刷题那 DHUOJ 这类在线评测系统对你来说可能还比较陌生。简单说OJ 就是一个机器判卷老师你提交一段代码它用预先准备好的测试数据跑你的程序然后把你的输出和标准答案做逐字符比对一致就给 ACAccepted不一致就返回 WAWrong Answer、TLETime Limit Exceeded之类的状态。我第一次用 OJ 刷题的时候其实挺懵的因为跟平时在 IDE 里写代码完全两个感觉。IDE 里你写完代码可以随便输出点什么看看效果OJ 不行。OJ 只认标准输入和标准输出多打一个空格、多换一行都算错。很多新手第一次提交 WA 不是因为逻辑不对而是因为没有理解输出格式四个字的分量。DHUOJ 的 118、119、120 这三道题被归为基础说明它就是给你上道用的。很多学校的算法课程、编程入门课都会把这类题作为作业或者上机练习。刷通这三道题你基本就掌握了 OJ 提交代码的完整流程也理解了程序是怎么跟评测系统打交道的。1.2 基础三连题的共同套路基础题之所以叫基础是因为考察的点非常集中变量、表达式、分支、循环、数组、字符串外加最基础的输入输出处理。118、119、120 这三道题虽然编号连着但侧重点不太一样——按我辅导过不少新手的经验来看它们通常覆盖了这么几个能力维度第一是读题能力。题目给了一个问题描述你能否从中提取出输入是什么、输出是什么、有没有多组测试数据、数据范围多大。这一关过不了后面写代码全是瞎写。第二是基本的语法和逻辑。比如循环的边界怎么控制条件判断的优先级数组下标会不会越界这些都是基础题的常客。第三是调试能力。OJ 不会告诉你哪组数据错了你必须学会自己造测试数据、自己推演程序运行过程用人肉跑程序的方式找到 bug。这三题如果你能独立 ACAccept通过就已经比很多人强了。因为很多同学卡在第一天——不是代码写不出来是根本不知道 OJ 上0 表示正常结束、EOF 表示输入结束这些黑话是什么意思。2. 做题前先把这些准备工作做好2.1 语言选型C 语言入门最稳DHUOJ 这类平台基本都支持 C、C、Java、Python 等主流语言。对刚起步的新手我强烈建议选C 语言原因有三个一是 OJ 的经典数据结构和算法模板绝大多数都是用 C/C 写的你以后看题解、参考别人的代码接触最多的就是 C 系语言。二是 C 语言的语法相对底层你对内存、数组、指针的理解会更扎实。三是最现实的原因——大部分学校的算法课程和程序设计竞赛评测平台和考试环境默认就是 C/C。当然如果你只是兴趣使然用 Python 也没问题。Python 写起来短读入用input().split()循环用range()比 C 的scanf友好得多。但要注意Python 在 OJ 上容易踩到性能的坑基础题还好往后做到复杂算法题时同样思路的 Python 代码很可能就 TLE 了。尤其是 120 这种可能涉及数组遍历和模式匹配的题如果数据量大用 Python 就要格外注意复杂度。我的建议是入门阶段用 C把语言基本功打牢如果实在觉得 C 的指针和内存管理劝退用 C 或者 Python 都行但一定要把一种语言吃透别今天 C 明天 Java 后天 Python换来换去最后哪个都不熟。提示在 DHUOJ 上提交代码时记得看清楚了题目的语言限制。有些题目明确写仅限 C/C有些则不限。选错语言编译不过那是浪费提交次数。2.2 输入输出格式OJ 的第一道坎这一节是重点中的重点基础题的 AC 率低一半以上挂在输入输出上。OJ 的输入输出有几种常见模式118/119/120 这类基础题基本逃不出这几个套路单组输入读一次数据输出一次结果。这种最简单代码直接写就行。多组输入已知组数先读一个整数 n表示后面有 n 组数据。这种就是外层套一个for循环。多组输入直到 EOF 结束不知道有多少组一直读到文件结束。C 语言里用while (scanf(...) ! EOF)Python 里用while True try: input()捕获异常。每组输入包含多行以特定标记结束比如先读一个数字读到 0 就结束。这种要特别注意退出条件。这三道基础题最常用的是前两种。但很多新手踩坑在于——不知道 OJ 的数据是多组的。你看题面里只给了一个整数 n就天真地以为只读一次结果只处理了一组数据就打印结果最后必然 WA。输出方面同样有讲究。绝大多数题目要求每个结果占一行或者结果之间用一个空格隔开。新手最容易犯的错就是多打印了空格——比如要求输出1 2 3你写成了1 2 3末尾多一个空格。这在人眼看来没区别但在 OJ 的逐字符比对下就是错。实操心得我辅导过的同学里至少有三分之一的人第一次提交 WA 是因为没处理多组输入或者输出格式多了一个空格。这两类问题排查起来最费时间因为逻辑本身没错是外在形式不对。每次做题拿到 WA先检查输入输出再看逻辑这个排查顺序能帮你省下大量时间。3. 三题拆解118、119、120 逐个击破3.1 118 题循环与累加的基础训练118 我印象里是很典型的入门第一题给你一个整数 n让你求 1 到 n 的某种运算结果比如求和、求阶乘、求奇数和。这类题核心考察的是循环结构。比如求 1 到 n 之和C 语言写法#include stdio.h int main() { int n; while (scanf(%d, n) ! EOF) { int sum 0; for (int i 1; i n; i) { sum i; } printf(%d\n, sum); } return 0; }别看这段代码短里面隐藏了好几个知识点第一while (scanf(%d, n) ! EOF)是处理多组输入的经典写法scanf读到文件末尾会返回EOF即 -1循环自然结束。如果你写成while (scanf(%d, n))也行因为成功读入返回 1读不到返回 0。但要注意这个返回值不是读到的值而是成功读入的变量个数新手很容易混淆。第二sum必须在每次循环开始前清零。如果int sum 0;写在while外面那第一组数据算完 sum 是 55第二组数据接着在 55 的基础上加结果就错了。这个坑我见太多了而且很难通过肉眼发现因为你单独跑一组数据永远是对的。第三for (int i 1; i n; i)的边界。很多人写i n导致少算一项。这种边界错误只有多组数据对比时才能察觉单看一组也可能正好撞对。如果你用 Python可以这么写while True: try: n int(input().strip()) except EOFError: break total 0 for i in range(1, n 1): total i print(total)注意range(1, n 1)的终点是 n1因为 range 是左闭右开区间。这里又多了一个语言特有的边界陷阱。3.2 119 题条件判断与边界处理的艺术119 这类题按常见基础题套路一般是给你几个数或者一个分数让你根据条件输出不同的结果——比如判断闰年、判断奇偶、判断成绩等级、比较三个数大小。核心考察的是分支结构和逻辑表达式。以判断闰年为例这是 OJ 界的常青树#include stdio.h int main() { int y; while (scanf(%d, y) ! EOF) { if ((y % 4 0 y % 100 ! 0) || y % 400 0) { printf(YES\n); } else { printf(NO\n); } } return 0; }闰年的规则是四年一闰百年不闰四百年再闰。这个题看着简单但几乎每年都有人错原因在于一是逻辑运算符优先级。的优先级高于||所以上面代码里的括号加不加结果其实一样但对于新手我建议永远加上括号别跟编译器玩心机。二是边界值比如 1900 年、2000 年、2100 年。1900 能被 4 整除但不是闰年2000 能被 400 整除是闰年2100 不能被 400 整除所以不是闰年。很多人只写了y % 4 0就完事结果 1900 这种用例直接挂掉。这类条件判断题有个通病题面给的示例数据往往非常温柔比如 2 不是闰年4 是闰年你光看示例根本测不出问题。真正的坑全在边界值上。怎么破我的习惯是拿到题先想极端情况最小输入、最大输入、恰好等于某个临界值的输入。比如判断奇偶就要想清楚 0 算奇数还是偶数——数学上 0 是偶数你写n % 2 0就是正确的。条件判断类的题建议你多造几组刁钻数据本地测试0 或者负数如果题目没说输入范围先试一下恰好等于边界条件的值很大的数检查是否会溢出119 题如果涉及多条件的排列组合还有一个小技巧把最简单的条件写在最前面用早退策略减少嵌套层数。比如判断成绩等级if (score 90) printf(A\n); else if (score 80) printf(B\n); else if (score 70) printf(C\n); else if (score 60) printf(D\n); else printf(E\n);这样写比多个if嵌套清爽得多逻辑也清晰。这种阶梯判断是最常用的条件处理模式新手要学会。3.3 120 题数组与简单算法思想的预热120 按基础题的进阶路线通常开始引入数组或字符串可能要求你统计、排序、查找、去重或者对一段文本做简单处理。这是从单变量操作到批量数据处理的关键一步也是很多新手第一次感到难的题。如果 120 是输入 n 个数输出最大值/最小值/平均值那么核心是用数组存储数据或者边读边处理。比如找最大值#include stdio.h int main() { int n; while (scanf(%d, n) ! EOF n 0) { int max -2147483647; // 用 int 的最小值初始化 int x; for (int i 0; i n; i) { scanf(%d, x); if (x max) max x; } printf(%d\n, max); } return 0; }这里有两个细节值得展开。一是max 的初值问题。很多新手写int max 0;如果输入全是负数就出错。正确做法是初始化为一个非常小的值比如 int 最小值或者先把第一个输入赋给 max再从第二个开始比。我习惯用int max -2147483647;因为这就是 int 能表示的最小值实际上是 -2147483648但 -2147483647 已经足够小了。二是要不要用数组的问题。如果题目只要求输出最大值/最小值/和用不着开数组每次读一个数处理一个就行。但如果题目要求排序、去除重复、按逆序输出那必须开数组存下来。分清这一点是个重要能力——不要为了用数组而用数组也不要因为老师讲了数组就要用而别扭地图数组。OJ 只看结果不看过程。如果 120 是字符串处理题比如统计一段文字中某个字符出现的次数、翻转字符串、判断回文那用的就是字符数组。C 语言的字符串处理特别容易翻车常见问题包括忘记在字符数组末尾留\0的位置使用gets被平台的警告信息搞懵字符串反转时只交换了一半的字符这里给个常见错误示例char s[100]; scanf(%s, s); // 这样没问题但只能读入不含空格的字符串如果题目要求读取含空格的整行字符串就不能用%s要用fgets(s, sizeof(s), stdin)或者gets(s)但是 gets 不安全很多平台已经禁用了。这个知识点在基础题里不一定考但以后必然遇到。实操心得三道题做完你会发现它们形成了一个小的能力闭环——118 让你会用循环处理一批数119 让你会针对不同情况做分支120 让你会批量存取数据并做简单操作。这个闭环就是后面所有算法题的地基地基不稳后面全是空中楼阁。4. 提交时最常见的错误与排查技巧4.1 编译错误先别急着怀疑题目我见过太多新手代码一提交就弹出 Compile Error编译错误然后火冒三丈地认为是平台的问题。其实九成九是自己代码的问题。常见原因头文件缺失。用了printf没#include stdio.h用了string.h的函数没#include string.h。C 语言里很多函数报错就是因为你没把说明书拿进来。变量名/关键字冲突。比如把变量命名为main、printf、scanf编译器直接炸。新手最容易犯的是用string做变量名这在 C 语言里没事但在 C 里string是类型名用int string之类就矛盾了。全角符号与半角符号混用。你在中文输入法状态下打了或者编译器根本不认识。这个错误在代码编辑器里往往不太显眼但编译错误信息会提示expected ;之类一看一个准。平台编译器版本差异。本地编译器能过的代码到了 OJ 的编译器上不一定能过。比如 C 语言里for (int i 0; ...)这种写法在 C99 标准下没问题但如果平台默认用 C89 标准就必须在函数开头先声明变量。这种差异最容易坑人。排查编译错误的方法很简单先把本地编译的错误信息读懂再逐条对照代码修改。千万别在一处语法错误没解决的情况下又改别的代码那样只会让错误信息越来越乱。4.2 运行时错误与超时一般不是玄学有时候代码编译过了提交后却返回 Runtime ErrorRE运行时错误或者 Time Limit ExceededTLE超时。这两类错误在基础题里其实很好排查。RE 最典型的原因是数组越界。比如你声明了int a[100]但读了 101 个数或者循环里访问了a[n]而这种下标是不存在的。C 语言对数组越界不做任何检查直接访问内存垃圾轻则结果错误重则段错误崩溃。另一个 RE 原因是除以零。如果题目输入里可能包含 0而你的代码没有先判断就去做除法那必然 RE。基础题里涉及求平均数的输入范围如果允许 0 个数据你就要小心。TLE 在基础题里出现得少但如果出现多半是死循环。比如while (scanf(%d, n) ! EOF)写成了while (scanf(%d, n))当读到文件末尾时scanf返回 0 或者 EOF如果处理不当循环条件永不满足退出条件程序就一直空转。另一种死循环是循环变量忘记更新比如for (int i 0; i n; )就漏了i。排查 RE 和 TLE 的思路是先在本地用最极端的数据范围跑一遍。比如题目说 n 最大 1000你就试试 n 1000题目说输入可能有多组你就准备一个非常长的输入文件喂进去。本地能复现问题就成功了一半。4.3 答案错误WA的自查清单WAWrong Answer是所有状态里最磨人的因为你不知道错在哪组数据。但正因为如此WA 一旦解决你的收获最大。我自己刷题这么多年总结了一个 WA 自查清单做基础题特别好用第一步重新读题。尤其是输入格式和输出格式两个部分。有没有多组数据输出之间用什么分隔数字前有没有符号位小数点后保留几位这些细节没看全必 WA。第二步检查初始化和清零。循环里需要清零的变量是不是定义在了循环外面数组有没有每次被重新赋值这是基础题最常见的隐性 bug。第三步逐个检查边界条件。比如i n还是i nn 1还是n还是跟边界值相关的每一处比较都值得停下来想一遍。第四步自己造特殊数据。如果一个题要处理的是 1 到 100 的数你就测 n 1、n 2、n 99、n 100。如果涉及字符就测大写字母、小写字母、数字字符、空格。把所有极端情况都跑一遍比盲目改代码有效率得多。第五步跟示例数据对照。OJ 题目通常会给出示例输入和示例输出你拿这段数据本地跑跑能对上再提交。但注意示例过了不代表一定 AC示例没过基本一定 WA。注意不要一 WA 就去看别人的题解。基础题的价值就在于让你自己调试、自己找错。看题解解决的是不会做而不是会做但错了——后者必须靠自己的排查能力。5. 刷题习惯养成从 118/119/120 到后续进阶5.1 建立读题-设计-编码-测试的标准流程我这几年观察到一个很普遍的现象很多新手拿到题就开始写代码写着写着发现自己漏了什么条件又回头改改完提交还是 WA再改折腾半天心态崩了。正确的顺序应该是先读题、再设计、后编码、最后测试。读题的时候用笔或者思维把三个问题问清楚输入是什么输出是什么处理规则是什么有些题故意把条件藏在两三段描述里你不会提取关键信息后面全是白做。设计的时候先不要碰键盘。用伪代码或者注释把思路写下来。比如先读 n循环 n 次读每个数比较大小更新最大值最后输出 max。这个过程逼着你把逻辑理顺大大减少后续改代码的次数。编码的时候一次写到位。写代码时保持函数代码块整洁变量命名有意义不要用a1、a2、x、y满天飞。基础题代码短命名的影响还不明显但好习惯从第一天就应该养成。测试的时候把示例数据过了还不算完。自己补几组边界数据。比如 118 题求 1 到 n你至少测 n 1结果是 1和 n 0如果题目允许结果应该是 0。119 题判断闰年你测 1900、2000、2100。120 题如果涉及数组你就测最大长度、最小长度。这套流程跑熟了刷题效率至少翻倍。5.2 基础题刷完下一步往哪走118、119、120 这三题 AC 之后别急着刷 121、122……先回头看自己做过的代码问问自己三个问题能不能不用循环嵌套就解决比如 118 题的 1 到 n 求和其实可以直接用等差数列公式n * (n 1) / 2时间复杂度 O(1)比循环 O(n) 快得多。基础题的巧妙之处在于你第一次用循环解出来是正确的但后面学了数学方法你会对复杂度这个概念有更直观的理解。有没有处理多组输入回头看自己的代码如果只处理了一组数据那说明你读题还不够仔细。把三题的代码都改成支持多组输入是个很好的巩固练习。代码能不能更简洁在保持可读性的前提下能不能减少变量数量能不能简化条件分支这不是炫技而是为后续复杂题目打基础——后面几百行的代码如果一开始就养成了简洁习惯调试会轻松得多。做完这三个回顾你就可以进入下一阶段的题目了排序冒泡、选择、插入、搜索、简单字符串处理、基础递归。你会发现自己在 118/119/120 上练熟的循环 分支 数组三板斧几乎是所有后续题目的核心骨架。5.3 关于卡题的正确心态最后说点题外的但很重要的东西——心态。我见过太多人在基础题上死磕一整晚越磕越烦躁最后 AC 了也没有成就感只剩下终于解脱了的疲惫感。其实卡题是正常现象每个刷过题的人都经历过。卡题的时间恰恰是你进步最快的时间。我的建议是一道题卡 30 分钟到 1 小时还没有头绪就先停下来把题面重新读一遍或者去休息一下干点别的。回来再看很多时候一下子就通了。这一小时内你已经把各种思路都试过一遍大脑虽然在休息后台其实还在处理这些问题。另外如果实在想不出来去看题解不丢人。但看完题解不要直接抄而是把题解关掉自己重新写一遍。能不看答案写出来才是真正学会了。看懂了和会写了之间隔着一条巨大的鸿沟。我在实际使用中发现刷基础题最有价值的部分从来不是我 AC 了三道题这个结果而是过程中反复读题、反复调试、反复思考边界条件的经历。这些经历会在后续遇到更复杂的题目时变成你下意识的分析能力。118、119、120 这三题 AC 之后你会明显感觉自己的调试能力上了一个台阶——至少不会再盯着屏幕里的 WA 发呆了。三道基础题说难不难说简单也不简单。把它们当成一块磨刀石把自己的代码能力和做题习惯好好磨一遍后面刷任何 OJ 都会顺手的多。