资讯详情

DHUOJ基础118 119 120刷题指南:从WA到AC的OJ入门核心技巧

📅 2026/10/18 2:20:30 | 华诺云谱 👁 阅读
DHUOJ基础118 119 120刷题指南:从WA到AC的OJ入门核心技巧
看到“DHUOJ 基础 118 119 120”这个标题时我第一反应是挺怀念的——每一个跑过在线评测系统的人几乎都从这种三位数编号的基础题起步。这三个题号放在一起基本可以断定是同一批入门题对应着顺序结构、分支结构、循环结构这些最底层的编程能力。很多人觉得 OJ 基础题不就是“算个和、判断个大小、打个循环”吗能有多难但真正上手之后才发现题本身不难难的是让你的程序被评测系统认可。同样的逻辑本地跑得好好的交上去就是 Presentation Error或者明明结果对却因为多了一个空格被判 Wrong Answer。这种事情没有人提醒自己可能要折腾好几个小时。这篇文章就围绕 DHUOJ 上的这组基础题把我自己刷这类题时积累的经验、踩过的坑、总结出来的排查方法完整拆开来讲同时也覆盖 OJ 入门必须知道的关键规则。适合刚接触 OJ、想在竞赛训练上迈出第一步的同学也适合那些反复提交却总是“莫名其妙”不过的人——你缺的往往不是编程能力而是对 OJ 判定机制的理解。1. 三道基础题到底在考什么1.1 为什么是 118、119、120要理解这三个题号先得明白 OJ 题目编号的逻辑。大部分 OJ 的题目号是按题目录入时间排序的而不是按难度排序所以 118、119、120 相邻并不等于它们属于同一个专题但它们带上“基础”这个标签后含义就完全不同了——这通常意味着这是系统收录的第一批、也是难度最低的一批题专门给新人试水用的。从出题套路来看这种三位数编号的基础题大概率覆盖三种典型结构数值计算与顺序结构给定输入按公式算结果原样输出。这类题考验的是最基本的读入、计算、打印能力很多 OJ 的第一题“A B”就是代表。条件分支判断根据不同的输入值走不同的分支可能是判断闰年、比较大小、算分段函数。重点考察 if-else 写得全不全面边界情况有没有考虑。循环与多组数据求 1 到 n 的和、统计个数、连续输出等。这里已经开始涉及循环边界和多组输入的问题是后面所有算法题的基石。当然我不能百分百确定你打开的 118、119、120 具体是哪三道原题因为不同 OJ 的题面可能完全不同。但从标签和题号规律来看它们几乎一定属于上面某个类型。学会这些类型对应的标准解法不管题面怎么变你都能找到下手的角度。1.2 三题背后的四大基本功我在帮一些学弟学妹看代码的时候发现基础题卡住一般不是“不会写”而是“基本功不扎实”。具体来说这组题考察的其实是四件事输入能不能稳稳接住OJ 的输入不是只有一个样例而是可能有一整组测试数据。如果你只写了scanf一次那程序就只能处理一个样例必然 WA。多组输入的标准写法是while (scanf(...) ! EOF)这个等下细说。循环边界算得准不准每次循环是i n还是i n下标从 0 开始还是从 1 开始这是最容易出错的地方。很多 WA 就是差这一个等号。类型范围踩得准不准两个 int 相加可能超过 2^31-1你以为算对了实际已经溢出成负数了。基础题同样可能暗含大数据提前用long long能避免很多麻烦。输出格式对不对OJ 题目对空格、换行有严格的要求。你有99%的逻辑都写对了但最后一个数字后面多打印了一个空格系统就会给你一个 Presentation Error。这在基础题中极其常见。这四项基本功光看书是练不出来的。只有在真实评测环境中被“打脸”几次才会形成本能的警觉。118、119、120 这套题的价值就是帮你用最小的代价完成这种警觉训练。2. 动手之前先把 OJ 的脾气摸清楚2.1 语言选择与编译环境DHUOJ 这类系统一般都支持 C、C、Java、Python 等主流语言。我的建议是如果这是一门面向竞赛训练的课程或平台优先用 C 或者 C不要用 Python。原因很实际。第一C/C 的执行效率高哪怕后面遇到时间限制很紧的题目也不会因为语言本身吃亏。第二OJ 的裁判机对 C/C 的输入输出支持最标准出错时排查起来更容易。第三竞赛圈的主流题解几乎都是 C/C 写的你以后想参考别人的思路语言相通会顺畅得多。如果你是初学者编译器建议选 Dev-C 或者 VS Code 配 MinGW都行。但提交到 OJ 时有一点必须记住代码开头要包含所需头文件C 用stdio.hC 用iostream或兼容 C 的cstdio。别在本地用一个头文件提交时忘了带上。一个很容易忽略的坑是老版本 OJ 使用的编译器版本可能比较旧C 标准停留在 C98 或 C11。你在本地用 C17 编译通过的代码交上去可能编译失败。稳妥的做法是尽量用 C 语言的函数风格scanf/printf而不是 C 的cin/cout这样兼容性最好性能也更好。2.2 输入输出格式的“隐形规则”OJ 题目的每一个描述都不是废话尤其是“输入格式”和“输出格式”两段。以“多组输入、每组占一行”为例题目可能会写“输入包含多组测试数据每组数据占一行”这意味着你的程序必须能处理多次输入直到文件结束。对应代码就是while (scanf(%d, n) ! EOF)EOF 是 End of File 的缩写表示读到文件结尾时停止。有些新人也写while (scanf(%d, n) 1)效果相同只是判断方式不同。这些写法在处理多组数据时是必须的如果你只写一次scanf系统输入第二组数据时你的程序已经结束了。输出方面有两个规则极其重要每行输出后面要有换行符。printf(%d\n, ans)的\n不要省。有些判题系统不要求最后一行有额外的空行但绝对不能少换行。一行内有多个数字时分隔方式必须和题目要求一致。题目说“输出两个数用一个空格分隔”那你就应该写成printf(%d %d, a, b)而不是printf(%d %d , a, b)——最后一个数后面的空格是多余的会触发 PE。还有一种情况是“两组输出之间空一行”这种处理方法是第一组正常输出从第二组开始先输出一个空行再输出结果。很多人第一反应是每组输出后再多打一个空行结果最后多了一个换行照样不过。2.3 在线评测的判定机制理解判定机制是你和 OJ “和解”的关键。一次提交系统会返回给你一个结果常见的有这么几种Accepted恭喜完全正确这是唯一绿色的结果。Wrong Answer输出结果和标准答案不一致要么逻辑有错要么格式有问题。Presentation Error格式错误但内容基本对。多空格、少换行都可能被这样判定。Compile Error编译没过通常因为头文件缺失、语法错误、或者用了过时的写法。Runtime Error运行时崩溃常见原因是数组越界、除零、栈溢出。Time Limit Exceeded超时算法太慢或者死循环。Memory Limit Exceeded内存超出限制比如数组开得巨大无比。你会发现里面没有“答案接近正确”的选项。OJ 就是非黑即白你差一点和差很多都是 WA。这种严格性一开始会让人觉得不近人情但它恰恰是算法竞赛训练中最有价值的部分——逼你对每一个细节负责。3. 118 题实操输入输出与多组数据的坑3.1 基础题最常见的形态虽然我没法看到你屏幕上 118 的原题但根据这类 OJ 基础题的规律“求和”类题目是出镜率最高的。常见的形态有两种输入两个整数输出它们的和只有一组数据。输入多组整数对每一对输出一个结果直到文件结束。第一种练的是“最基础的读入输出”第二种练的就是多组数据处理。118 如果只有一组输入算法上没什么好说的但如果它是一道多组输入题那就值得认真对待。多组输入题有一个非常经典的版本输入若干行每行两个整数 a 和 b遇到文件结束停止每行输出 a b。这个题目在很多 OJ 上存在了十几年直到现在仍然是新人接触的第一个“非傻瓜题”。3.2 直接能用的参考代码下面给出多组 AB 的完整参考代码用 C 实现#include stdio.h int main() { int a, b; while (scanf(%d %d, a, b) ! EOF) { printf(%d\n, a b); } return 0; }就这么几行但里面有三个关键点值得展开。scanf的返回值scanf成功读入两个整数时返回 2读入一个时返回 1没有读到任何东西时返回 EOF。while (scanf(...) ! EOF)的含义是“只要还能读入数据就一直处理”这是处理多组输入的万能写法。%d %d中间的空白scanf 在处理数字输入时会自动跳过空格、换行、制表符所以%d %d、%d%d、%d %d在这里效果几乎一样。建议保留空格可读性好一点。printf后面的\n这个换行是必须的。题目要求“每组输出占一行”你就要输出一行。如果没有\n下一次输出会直接接在上一行末尾结果连成一串必挂。3.3 为什么这么写最稳以及常见翻车点有人会问为什么不用while(1)配合break理论上也可以但你需要额外判断什么时候停止代码容易写错。用EOF判断是把结束条件交给系统输入流干净利落。翻车点主要集中在三个方面int 溢出如果 a 和 b 的范围是2 * 10^9两者相加已经超过 int 的表示上限了此时要改用long long#include stdio.h int main() { long long a, b; while (scanf(%lld %lld, a, b) ! EOF) { printf(%lld\n, a b); } return 0; }注意scanf里long long的格式符是%lld不是%d。这个很多新手第一次都会写错编译的时候会报警告但程序还能跑结果却完全不对。题目要处理到“遇见 0 0 结束”而不是“文件结束”这种情况下while(1)加if (a 0 b 0) break;才是正解。读题时一定要看清楚终止条件是什么。输出格式的隐藏要求如果题目要求“每组输出后跟一个空行”而非“每组输出占一行”代码就要改成printf(%d\n\n, a b);这看起来只是多了一个\n但判题结果是完全不同的。我见过很多人在这种地方反复提交七八次每次都是 Presentation Error最后才发现是空行问题。4. 119 题实操循环、累加与边界条件4.1 大概率是循环类问题到了 119难度通常会略升一点但不会突然变得很难。从这类题的位置来看它很可能涉及循环和累加。比较常见的是以下几类求 1 到 n 的和n 由输入给定。这是最典型的循环入门题。判断一个数是否为素数需要循环到sqrt(n)左右。求阶乘n 可能给到 10 到 20需要考虑溢出。我这部分以“求和 12...n”和“素数判断”这两个最经典的方向展开因为它们覆盖了循环题的全部核心技巧。4.2 参考解法累加求和假定题目输入一个整数 n输出 1 2 ... n 的值#include stdio.h int main() { int n; while (scanf(%d, n) ! EOF) { long long sum 0; for (int i 1; i n; i) { sum i; } printf(%lld\n, sum); } return 0; }为什么 sum 要用long longn 如果取到 1000001 累加到 100000 的结果是 5000050000已经超过int能表示的最大值约 21 亿所以必须用更宽的类型。这是一个非常典型的“看起来简单但暗藏陷阱”的基础题设计。循环边界方面i n是正解。如果你写i n当 n 5 时只加到了 4结果少 5。这种错在样例输入较小的时看不出来一旦换到大 n 就会 WA。不过这类题如果 n 很大比如到 10^9O(n) 的循环就会超时。这时候要用等差数列公式sum n * (n 1) / 2。但结合“基础”标签n 一般不会大到必须用公式的程度。学有余力可以了解一下竞赛里效率是永恒的话题。4.3 参考解法判断素数如果 119 是素数判断那知识点就更多了。最原始的写法是循环 2 到 n-1逐个检查能否整除优化一点是循环 2 到 sqrt(n)。后者在大数时优势明显我直接给优化后的版本#include stdio.h #include math.h int is_prime(int n) { if (n 2) return 0; if (n 2) return 1; if (n % 2 0) return 0; for (int i 3; i sqrt(n); i 2) { if (n % i 0) return 0; } return 1; } int main() { int n; while (scanf(%d, n) ! EOF) { printf(%s\n, is_prime(n) ? Yes : No); } return 0; }这里有几个细节n 2时一定不是素数1 既不是素数也不是合数这个特判必须有。偶数直接淘汰循环从 3 开始每次加 2只检查奇数因子效率翻倍。sqrt(n)需要引入math.h头文件。有些 OJ 在 O2 优化下可能对sqrt有精度警告更稳的写法是i * i n用乘法避免浮点误差。素数判断和求和的代码结构其实是一致的多组输入用 while EOF输出记得换行。掌握了这个框架循环类基础题基本就通了一半。4.4 循环题最重要的边界意识刷 119 这类题最大的收获不是会写循环而是建立“边界意识”。空输入如果输入 n 为 0 或者负数循环还会执行吗要不要输出 0 或特殊结果以题目要求为准。单次累加溢出题目说 n 的范围是0 n 10^6那n * (n 1) / 2在计算过程中可能先溢出再变小。如果直接用公式这里的 n 和乘积要用long long。这种“先溢出再除”的坑我在实际调试中遇到不止一次。多组数据之间的变量重置很多人在循环里定义了 int i却没有重置 sum上一组数据的结果会带到下一组。所以 sum 一定要定义在 while 循环内部或者每组开始前手动置零。这是新手最容易犯的“结构性错误”。5. 120 题实操分支、数组和排序入门5.1 从“一个数”到“一组数”如果 118 是单点计算119 是循环处理一个数那 120 大概率就开始接触数组了。数组是编程学习的第一道分水岭很多人就是从这里开始觉得“题目变难了”。常见的出题方向有这么几类输入 n 个数找出最大值/最小值并输出。输入 n 个数按升序或降序输出。输入一个整数数组进行逆序输出。输入若干成绩统计及格人数和平均分。不管是哪一道核心能力都是“用数组把数据存下来再用循环去遍历处理”。这正好对应了编程里最朴素的理念先存储再处理。5.2 参考解法找最值假设题目输入一个整数 n然后输入 n 个整数输出其中的最大值#include stdio.h int main() { int n; while (scanf(%d, n) ! EOF) { int arr[1000]; for (int i 0; i n; i) { scanf(%d, arr[i]); } int max arr[0]; for (int i 1; i n; i) { if (arr[i] max) { max arr[i]; } } printf(%d\n, max); } return 0; }关键在于max的初始值。我用的是arr[0]因为如果初始为 0当输入全是负数时最大值会被错误地算成 0。用数组第一个元素做初始值是最稳妥的方案因为它是真实存在的输入数据。还有一个思路是不用数组在读入时用一个变量实时更新最大值。这样的空间复杂度更低但暴露了不同的思维角度。基础题用数组会更直白尤其后面你可能还需要“输出第二大值”或者“逆序输出”数组是必须的。5.3 参考解法冒泡排序排序是算法里绕不开的课题120 这种入门位置如果出现排序用冒泡排序或者选择排序就够了#include stdio.h int main() { int n; while (scanf(%d, n) ! EOF) { int arr[100]; for (int i 0; i n; i) { scanf(%d, arr[i]); } for (int i 0; i n - 1; i) { for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; } } } for (int i 0; i n; i) { if (i 0) printf( ); printf(%d, arr[i]); } printf(\n); } return 0; }冒泡排序的效率是 O(n^2)对基础题的数据量完全够用。你可能会在网上看到快速排序的代码但那个阶段切记不要盲目追求高级算法先理解清楚冒泡排序的“相邻比较交换”思想后面再学快排时反而更容易上手。输出部分的写法很讲究输出一行数字数字之间用空格分隔最后一个数字后不能有额外空格。我用if (i 0) printf( );在每次输出前先输出一个空格这样最后不会出现尾巴空格。这道题很多人卡在 PE 上原因就在这。5.4 数组越界与固定大小的选择数组大小是另一个隐蔽问题。题目如果只说“输入 n 个整数”而没说 n 的最大值很多新人会随手写int arr[10]结果 n 稍微大一点就越界访问出现 Runtime Error 或者一堆乱码。我的建议是先看题目给的数据范围数组开得比最大值大一点比如 n 1000 就开int arr[1005]留一点余量。如果题目实在没给范围稳妥地开到 100000 也不会真的占多少内存OJ 的内存限制通常足够大。但也不要无脑开int arr[1000000]那可能直接 MLE。另外用memset初始化数组也是好习惯尤其是后面对数组做计数统计时不清零会带上一次残留数据结果很难排查。6. 提交报错排查从 WA 到 AC 的完整路径6.1 常见报错速查表很多时候你觉得代码“明明没问题”提交却一路红灯。这不一定是你逻辑错了也可能是误触了 OJ 的雷区。我整理了一个速查表照着排查能省很多时间错误类型 可能原因 排查思路Compile Error 头文件缺失、main 函数返回类型错误、用了 gets 等过时函数、编译器版本旧 先看系统给出的编译错误信息那行会直接指到代码的某一行。C 代码尽量用标准写法Wrong Answer 逻辑错误、边界条件漏判、变量类型溢出、输出格式不正确 先核对样例再猜边界数据检查循环边界和 if 条件Presentation Error 多空格、少换行、空行位置不对 把输出复制到文本编辑器里打开“显示空白字符”一眼就能看到多余空格或换行Runtime Error 数组越界、除零、递归过深 检查所有数组下标检查除法分母是否可能为 0Time Limit Exceeded 算法复杂度太高、死循环、输入输出太慢 检查循环边界是否永远达不到退出条件考虑换更优算法MLE 数组开得过大、递归层数太多、没及时释放内存 看数据范围上限合理设置数组大小6.2 我自己常用的排查流程遇到 WA 时不建议直接乱改代码碰运气。我完整的排查路径是这样的你可以直接照着用跑一遍题目给的样例。如果样例都过不了说明基础逻辑有问题先打印中间结果。构造边界数据。比如题目说 n 是正整数试 n1如果题目没给范围试 n0、n负数甚至不输入任何数据看程序会不会崩。检查输出格式。用眼睛看不出来时把程序输出重定向到文件里再用十六进制查看或编辑器显示空白字符确认每一行结尾、每个空格都和题目要求一致。重读输入输出格式说明。很多 WA 不是程序问题而是“理解问题”。比如题目说的是“输入整数 n再输入 n 行”而你写成了“每行第一个数代表个数”结构上就错位了。换一种输入输出方式。有时候cin/cout会因为缓冲区同步问题超时改用scanf/printf后立刻 AC。这不是玄学C 的流在大量输入时确实比 C 风格慢。最后确认自己读题时有没有漏掉“多组输入”“直到文件结束”“0 0 结束”这类关键词。这三个条件对应的代码框架完全不同一旦写错输出结果永远对不上。6.3 同一个问题反复提交不划算最后想分享一个提高效率的习惯每道题最多连续提交两三次如果还是不对就从“改代码碰运气”切换到“对照题目重新分析”的模式。OJ 不是老虎机它不会因为你多点几次提交就让结果随机变绿。反复提交没有任何收益只有把逻辑从头缕一遍才是真正的解决路径。我见过太多人在一个基础题上刷了十几二十次提交记录最后发现代码逻辑从一开始就理解偏了。这时候真正该做的不是盯着屏幕发呆而是把题面的每个字读一遍把自己写过的代码每个分支都口算一遍甚至干脆先别改代码拿几张纸手算几个例子。7. 刷完这套题之后怎么继续提升7.1 把三道题拓展成三类模板118、119、120 刷完之后很多人会急着往后刷 121、122但我的建议是先回头总结三个模板多组输入框架、循环边界处理、数组遍历输出。这三个能力几乎会出现在你之后所有的题目里是真正的“基础中的基础”。你可以把这三道题加入自己的代码库或者整理成笔记。不要小看这一步很多人刷题只追求 AC 数字增长却从不回头看自己的代码结果换一个月后再写同样类型的题依然会犯同样的错。把模板沉淀下来才是提升的起点。代码库不需要多高级新建一个文本文件写上这三个模板的核心代码和注释就行。以后遇到新题先翻模板找到当时编写的思路你会发现自己的效率高了不少。7.2 向下一个难度迈进的方向如果你现在能不看任何提示独立把多组输入的循环代码和带数组的排序代码写出来你已经具备继续前进的基本条件。接下来的路线大概是从线性结构到查找和排序算法理解二分查找、选择排序、插入排序这类基础算法。从单点问题到区间问题开始接触前缀和、差分等技巧。从“自己造轮子”到“使用现成容器”开始接触 C 的 STL比如 vector、sort。当然不用急着一口气全学。竞赛训练最忌讳的就是贪多嚼不烂。把 118/119/120 这组基础题吃透比你潦草地刷完 20 道题更有价值。7.3 关于刷题心态的一点体会我也想聊聊心态。我在 OJ 上见过两类同学一类是卡了几次就怀疑自己不适合编程另一类是每道题刷到 AC 就很兴奋但从不分析为什么 WA。前者容易放弃得太早后者容易走得不远。正确的节奏是WA 不可怕可怕的是不知道错在哪里就胡乱提交。你每一次收到 PE 或者 WA都应该把它当成一次“系统在教你怎么精确表达”的机会。OJ 的基础题看起来只是简单计算但真正的收获是你理解了什么叫“你的程序必须和机器达成完全一致的理解”——这个能力在以后的工程开发里同样重要。我自己刷这类题时最大的体会就是OJ 是一个极其诚实的伙伴你对就是对错就是错没有半分糊弄。你需要的不是天赋而是耐心读完题、仔细想清楚边界、认真检查格式。做到这三点基础题想不过都难。如果你也在刷同一个系列的题欢迎把这篇文章里的代码当模板去套但更重要的是套完之后想一想如果题面改成“输出平均值”“逆序输出”“只输出偶数”你的代码还能不能扛住能想清楚这些问题这组基础题你才算是真正吃透了。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑