ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

东华OJ 89-93题复盘:从WA到AC的边界条件与输入输出实战指南

东华OJ 89-93题复盘:从WA到AC的边界条件与输入输出实战指南 最近在东华OJ上刷题正好把编号区间 89 到 93 的题目完整过了一遍。这五道题卡了我两个晚上不是难度有多高而是里面有几道题对输入输出和边界条件的考察特别细稍不注意就是 WA 或者 RE。身边不少同学刷到这一段落也容易卡壳所以专门写一篇复盘把题型思路、踩坑点和排查方法都梳理清楚给正在刷东华OJ的朋友做个参考。这一区间放在整个 OJ 题库里属于从入门语法向基础算法过渡的阶段。题面不会太长算法考察也不深但很考验读题能力和代码规范程度。如果你正在准备期末考试、竞赛入门或者单纯想补一补 C 语言的基础这篇博文覆盖的内容基本都能用得上。1. 东华OJ与89-93题目段的整体定位1.1 东华OJ是什么89-93为什么值得单独拿出来说东华OJOnline Judge是东华大学的在线判题系统和绝大多数高校 OJ 一样它做的事情很简单你提交一份代码系统用预设的测试数据去跑跑完对比输出结果全对就给 AcceptAC否则返回 WA答案错误、TLE超时、RE运行时错误等状态。校内同学主要用它完成课程作业也有不少人拿它当竞赛入门训练场。题库里的题目通常按知识点分块编号越靠后综合度越高。89 到 93 这个区间很有意思它在题库中的位置恰好处于一个过渡段纯语法题已经刷完开始进入需要自己设计逻辑的“小题”。这五道题本身并不算难但每一道都代表了一类常见题型。把它们彻底吃透后面刷 100 以上的题会顺很多因为很多复杂题无非是这些基础套路的组合。提示不同学校的 OJ 题库编号规则可能不同但“编号区间”作为阶段性划分的思路是通用的。如果你用的是其它学校的 OJ同样可以通过观察题型变化来找自己的薄弱环节。1.2 这个区间覆盖的核心考点先说结论89-93 这一段主要覆盖了四个能力点模拟题按规则实现、输入输出的规范性、边界条件处理、基础数据组织。模拟题题目给出一套规则你按规则一步步算就行不需要精巧算法。这类题练的是“把自然语言翻译成代码”的能力。输入输出规范很多题目不是一次测试一组数据而是“多组输入直到 EOF”或者每行包含多个字段。scanf 的返回值怎么用、EOF 怎么判断、数组开多大这些都是这一段的隐形考点。边界条件比如判断一个数是否是素数2 要不要算比如冒泡排序到第几轮提前结束比如数组下标从 0 还是从 1 开始。OJ 的测试数据里一定会带上边界样例处理不好就 WA。基础数据组织用数组存一组数、用结构体存多条记录、用字符串做简单处理。这个阶段还不需要链表、树、图但数组和结构体的灵活运用是必须过关的。1.3 刷之前先明确心态AC率比“AC数量”更重要我见过很多同学刷 OJ 有一个误区拼命刷题量同一页题目刷过去就算完事WA 了也不管为什么改到过就下一题。这样刷十道题不如认真吃透五道题。89-93 这个区间非常适合用来练“一遍过的能力”。因为题目本身难度有限如果你愿意花十分钟读题、五分钟设计思路、再花几分钟把边界想清楚一次 AC 的概率会大幅提升。这种习惯一旦养成后面遇到复杂题目时调试成本会低很多。2. 刷题前必须处理好的四件小事2.1 读懂 OJ 的输入输出约定比写代码更重要很多人第一次刷 OJ 最常见的翻车点就是没搞懂输入输出的格式。东华 OJ 的题目输入描述会写明“输入包含多组测试数据”还是“输入只有一组数据”这两种情况的写法完全不同。单组数据直接读就好int n; scanf(%d, n);多组数据到 EOF 结束则需要这样写int n; while (scanf(%d, n) ! EOF) { // 处理每一组 }这里的关键是 scanf 的返回值它返回成功读取的变量个数读不到数据时返回 EOF。只要记住这一点就不会在输入格式上吃亏。2.2 数组和变量初始化不要赌默认值C 语言里局部变量不初始化里面是随机值全局变量才会默认置 0。我在刷题时吃过一次亏定义了一个计数器没初始化本地跑偶尔能过一交上去 WA。后来养成习惯所有变量声明完立即赋初值数组用 memset 统一清零再没有因为这种低级问题翻过车。如果数组需要清零使用int a[105]; memset(a, 0, sizeof(a));注意 memset 定义在 string.h 头文件里。还有一个常被忽略的坑数组长度不是越大越好但要保证超出题目给定范围至少两个单位防止越界访问。2.3 用样例反推题目意图再动手写这部分是实操经验。拿到一道题先别急着写码先把题目给的输入样例手动算一遍算出输出样例。这个过程能帮你验证自己对规则的理解是否正确。比如题目说“将数组中的奇数按从小到大的顺序排列”你手动拿样例走一遍如果结果和输出样例一致说明你理解的是对的如果不一致说明你漏了某个条件比如“奇数排序后放在原位置其它数不动”。这个步骤只要两分钟但能省掉后续半个小时的调试时间。2.4 编译器警告当成错误处理不管你用的是 Dev-C 还是 Code::Blocks编译时冒出来的 warning 一定要看。我在代码里写过scanf(%d, a)漏了取地址符编译是过的有时候只是 warning但运行结果完全不可控。OJ 系统判题时用的是另一套编译环境你本地能跑的代码在服务器上可能行为不同所以所有警告都要处理干净。3. 89-93题型的典型拆解与解题套路口诀3.1 模拟题把规则翻译成循环和条件这一类题目在东华 OJ 中很常见题面描述一个过程让你模拟结果。比如“给定一个初始数字按规则反复操作问第 N 次的结果”这类。解题套路固定在四步把题面中的规则拆成不可再分的小步骤。用变量表示当前状态。用循环控制步骤的重复次数。在每次循环结束前更新状态。举个例子题目要求“从 1 开始如果当前数是偶数就除以 2否则乘以 3 再加 1直到变成 1”这就是经典的考拉兹猜想模拟题。代码只需要一个 while 循环int x 1; while (x ! 1) { if (x % 2 0) { x / 2; } else { x 3 * x 1; } }模拟题的难点不在语法而在“翻译”的准确性。很多人写着写着就把条件写反了或者把边界条件搞错导致无限循环。每次写完循环后用手动模拟一次小数据确认结果这是一定要养成的动作。注意模拟题最怕的就是题目里隐藏的“特殊情况”。比如“直到变成 1”这个条件如果初始输入就是 1循环一次都不应该执行。这类边界之初值一定要先判断。3.2 排序类题目先选对算法再处理稳定性这一区间有几道题涉及排序但数据规模不大不需要上排序算法里的什么快排、堆排冒泡和选择排序完全够用。排序本身不难容易出问题的是排序规则的定制。比如题目要求“按成绩从高到低排序如果成绩相同按学号从小到大排序”那么比较的写法就是if (a.score ! b.score) { return a.score b.score; } else { return a.id b.id; }如果用的是标准库 qsort 或 C 的 sort切记比较函数返回值的写法要严格符合规范。我自己就犯过这种错比较函数里返回了“相等返回 1”直接导致排序结果不确定OJ 反复 WA。如果你还不太熟练 qsort 的函数指针写法这个阶段用冒泡手动处理反而更稳妥至少逻辑在自己掌控内。3.3 字符串处理每一次下标都要停下来想字符串题目这一段大概率涉及比如统计字符个数、判断回文、找子串。C 语言字符串以 \0 结尾处理时必须时刻记住这一点。统计字符个数的典型写法char s[1005]; int cnt[256] {0}; scanf(%s, s); for (int i 0; s[i] ! \0; i) { cnt[(unsigned char)s[i]]; }这里有两个容易踩的坑。第一个字符数组的长度要留出最后一个字节给 \0所以如果题目说“字符串长度不超过 1000”数组至少要开 1001我自己习惯直接开到 1005 或更大。第二个用 char 做数组下标时可能是负数如果 char 有符号所以要转成 unsigned char。这类细节不处理本地测试没问题OJ 上一跑就崩。字符串类型题目里还有一类“给定一行包含空格的句子”的情况。这个时候scanf(%s)读不了含空格的字符串要用 fgets 或 gets注意新版编译器对 gets 有警告可改用 fgets。这个点很多人不知道读到空格就截断输出一直对不上。3.4 数学规律题不要暴力硬算先手算找周期89-93 里大概率会出现一两道看起来是模拟但本质是数学规律的题。比如“某个数列的第 N 项”如果 N 很大每次都循环计算会超时TLE。这类题的特征很明确操作规则简单但要求的项数很大。正确的做法是先在小范围内手算或写个程序跑出前几十项观察是否有循环节周期性。找到循环节之后用取模运算把 N 映射到一个小范围内就能直接输出答案。打个比方这就好比你看一部电视剧如果发现了剧本每 10 集就是一个循环那问第 99 集的内容就不用真的看到第 99 集直接看第 9 集就行。这类题一旦你想通代码往往非常短但前提是你愿意做这个“找规律”的动作。很多同学拿到题就写循环不做数学层面的观察TLE 了还纳闷优化方向其实是方向一开始就没走对。4. 判题状态与排查思路从WA到AC的完整闭环4.1 常见判题状态速查不管在东华 OJ 还是其它 OJ你提交后看到的状态无外乎下面这几种。把它们背熟相当于拿到了一张排错地图状态含义常见原因AC通过无WA答案错误输出格式不对、逻辑错误、边界条件漏判TLE超时算法太慢、死循环、多组输入没终止RE运行时错误数组越界、除零、野指针CE编译错误语法错误、头文件缺失、选错语言PE格式错误多换了行、多打了空格PE 一般算是“最接近 AC 的错误”。如果你看到这个状态说明你的逻辑正确只需要把输出格式调成和题目要求一模一样。4.2 WA不要瞎改用“最小化样例”定位WA 是刷题时最让人崩溃的状态。我的排查方法分三步第一步检查输出格式。是不是多打了空格是不是没有换行题目要求“每行输出一个结果”你写成了一行输出所有结果肯定 WA。这种错误用肉眼对比输出样例就能发现。第二步检查边界条件。这里有一个很实用的方法构造极端输入比如数组只有一个元素、输入的数字是最大值、n0、字符串为空串等。如果某个边界输入下程序输出明显不对问题就定位了。第三步检查中间数据。如果前面两步都没发现异常就在代码里加 printf 把关键变量的值打印出来看看每一步计算是否符合预期。确认无误后删除调试输出再提交。我一直强调不要用“猜”的方式修 bug。改一行交一次再改一行这种碰运气式的刷题方式真的是在浪费时间。学会加打印、看中间值才是正经排错方式。4.3 TLE先确认是不是死循环再谈优化TLE 分两种情况一种是程序真的慢另一种是死循环。死循环最常见的原因是 while 的判断条件写错了比如应该判断n ! 0写成了n 0或者多组输入时循环终止条件不成立读不到数据就进入死循环。排查死循环的方法很简单在循环体末尾加一个计数器如果循环次数超过预期就打印出来。比如int cnt 0; while (...) { // ... cnt; if (cnt 1000000) { printf(dead loop!\n); break; } }如果程序本身逻辑没错只是数据规模大导致超时那就要考虑换算法。比如从冒泡排序换成快排把这个 O(n^2) 的解法变成 O(n log n) 的。这种优化思路在后面的高难度题里会更常用。4.4 RE十有八九是数组越界如何快速定位RE 最让人头疼因为程序可能跑着跑着就崩了什么输出都没有。先说结论RE 的第一嫌疑永远是数组越界。数组越界往往发生在循环下标控制不当的时候比如for (int i 1; i n; i) { a[i] ...; // 如果 a 只定义了 a[100]这里就可能越界 }解决办法是数组开大一点。我习惯在题目给定范围基础上加 5 到 10 的余量这样即使用到了下标 1 到 n也足够安全。还有一种 RE 是除零。如果题目中有取余、除法操作务必先判断分母是否为 0。比如求平均数的时候如果题目允许 n 0而你直接算 sum / n那就必然 RE。5. 实操阶段怎么把89-93当成一次小项目来做5.1 建立自己的刷题记录表我发现很多同学刷 OJ 时没有记录习惯刷完就忘过了几天再遇到同类题还是不会。个人经验是做一个简单的表格每道题一行记录题目编号、题型、首次提交状态、错误原因、解决思路。我自己的表格大概长这样题号题型核心知识点首次状态错误原因复刷情况89模拟循环与条件WA边界条件漏判一次通过90排序结构体比较AC无不需要复刷91字符串字符统计WA数组越界加深印象92数学循环节TLE暴力计算重点复习93模拟状态模拟RE除零重点复习这份表的价值在于你把它积累起来就能看清自己的知识盲区。比如连续五六道题都因为数组越界 WA那下一步重点就该练数组和下标控制。5.2 一题多解用不同算法实现同一道题对某一区间题目的最高效率学习法就是拿一道题进行一题多解。比如排序题分别用冒泡、选择、插入甚至 C 标准库的 qsort 各写一遍。表面上看是浪费时间实际上这种训练能帮你把排序算法的每个细节都刻进脑子里。同样的道理模拟题可以用循环写也可以用递归写如果数据规模支持。写第二遍时你往往会发现第一遍代码里某些写得很别扭的地方这个发现本身就是特别宝贵的经验。5.3 边界用例构造给自己当 OJ 判题员题目不会告诉你测试数据是什么所以你要学会自己构造测试数据。从 89-93 的角度说以下三类用例是必须准备的规模最小的输入比如 1 个数、1 行字符串。规模最大的输入按题目限制的最大值。相同元素所有输入都一样或者包含\0字符等特殊值。我刷题时有个习惯——在动手写代码之前先把这几组用例写到本地代码写完后先跑这些用例全部通过后再提交。习惯养成之后一次 AC 的几率可以提高很多。这个做法说起来简单但实际操作中我发现绝大多数人不愿意做因为太麻烦。可正是这个“麻烦”的步骤把刷题效率和真实能力分开了。6. 实战复盘把89-93作为方法论的试验场6.1 时间管理一道题卡多久该跳过刷题时最怕陷入“不甘心死磕”的状态。我给自己定了一个规则一道题如果提交超过 5 次仍然不是 AC就先放下去做别的事或者去睡觉。第二天再回来大脑清醒之后往往一眼就能看出来问题在哪。这不是逃避而是给思维松绑。很多逻辑死结在原地绕不出来是因为你陷在了自己想当然的假设里。过几个小时回来以“重新读题”的心态看代码很容易发现之前视而不见的错误。89-93 这个阶段题目本身难度不大熬夜死磕带来的收获远不如休息后再看。6.2 从 WA 到 AC 的反复这是正常过程如果你看到自己反复 WA也别太灰心。我刷这一区间时有一道题连交了三次 WA当时很烦躁觉得题目这么简单怎么会错。后来仔细一查发现是题目要求的是“每行输出后空一行”而我只是简单换了行。这种输出格式的细节比算法本身更能决定 AC 还是 WA。把每一次 WA 当成一次“和 OJ 的对话”它会告诉你你没有完全理解题目要求。这种反馈其实是特别宝贵的因为它比考试打分更直接告诉你具体哪里有问题。抱着这样的心态去刷题就不会觉得 WA 是挫败而是成长线索。6.3 对照他人代码但要先有自己的版本刷完一道题后可以去看看别人是怎么写的。东华 OJ 一般不能直接看别人代码但可以用题目描述和思路去搜索引擎、论坛找同类题的解。我个人建议先自己 AC 了再看题解这样不会被固化思维带偏。对照时要关注的不是“答案对不对”而是“别人哪里写得比我简洁”“别人如何处理了我没注意的边界”。“简洁”不是指代码行数少而是逻辑结构清晰变量命名可读注释到位。这些习惯在你以后做项目、写毕设时都会受用。7. 从这段题目往后的进阶方向7.1 知识层面的下一站数据结构基础如果 89-93 区间刷完且思路已经清晰下一步建议进入数据结构专题。链表、栈、队列、二叉树这些内容在后续题目中会大量出现。数据结构题的核心难点在于“选对结构”而不是“实现结构”。提前了解一下每种结构的适用场景后面做题会轻松不少。比如“需要频繁在头部插入删除”就用链表“需要先进后出”就用栈“需要按优先级处理”就用优先队列。这些结论看起来很基础但真到了做题时很多人还是习惯什么都用数组硬刚最后复杂度爆炸。7.2 思维层面的进阶从“会写”到“会算”这一阶段还有一个任务开始建立时间复杂度的概念。89-93 的题目数据规模往往很小暴力不会超时但你不能一直停留在“暴力能过就行”的舒适区。后面的题目数据规模会越来越大如果你总是用 O(n^2) 的算法硬算TLE 只是时间问题。学一点大 O 表示法知道在 1 秒时限内10^6 的数据量最多做 O(n) 级别运算10^4 的数据量可以用 O(n^2)。这个意识建立起来之后你做题的思路就会不同看到数据规模第一反应是“这题要用什么复杂度级别的算法”而不是“怎么照着题意敲代码”。7.3 习惯层面的进阶把 OJ 当日常训练而不是临时抱佛脚刷 OJ 最有效的频率不是一晚上刷十道而是每天两题、坚持一个月。前面说的记录表、一题多解、自己造边界用例这些方法只有持续做才能见效。如果你只是期末考试前突击一周那大概率只会有 AC 数量的快感很难有扎实的算法底子。还有一点刷题要尽量少依赖 IDE 的自动补全和调试器。考试和比赛环境往往没有这些辅助功能如果在平时练习中不刻意锻炼裸写代码的能力上了考场会发现自己连变量名都拼不对。8. 踩坑实录这段题目里最常见的三处翻车8.1 输出答案带上了多余的逗号或空格这类问题在“输出数组元素”的题里很常见。比如要求输出排序后的结果元素之间用空格分隔最后一个数后面不能有空格。很多人的写法是for (int i 0; i n; i) { printf(%d , a[i]); }这样最后一个元素后面也会输出一个空格OJ 判题时通常会把这些和标准答案不一样的部分判成 WA。正确处理方式有两种// 方式一第一个元素前不带空格 for (int i 0; i n; i) { if (i) printf( ); printf(%d, a[i]); } // 方式二最后一个元素后不带空格 for (int i 0; i n; i) { printf(%d, a[i]); if (i ! n - 1) printf( ); }别小看这种细节它在 OJ 的 WA 汇总里占了很大的比例。8.2 多组数据时忘了重置状态如果题目是“一次输入包含多个测试用例每个用例第一行是整数 n”那么每一组数据之间状态必须是独立的。计数变量要清零、数组要重置、标志位要重新初始化。我见过有人把cnt定义在循环外面忘了在每组数据里清零导致后面的数据统计全部累加结果错得离谱。解决办法也很简单把循环内部的变量尽量定义在循环体内部作用域越小出问题的概率越低。while (scanf(%d, n) ! EOF) { int cnt 0; // 每一组都重新初始化 // 处理逻辑 }8.3 scanf 格式化字符串里的空格这是一个非常隐蔽的坑。比如scanf(%d, %d, a, b);如果你在格式化字符串里写了逗号空格那么输入时也必须完全按这个格式来逗号前不能多个空格逗号后不能少个空格否则读入就可能失败。最简单的建议scanf 的格式化字符串里不要写任何多余字符需要读两个整数就直接写%d%d它天然会跳过空白字符。这类问题在本地很难发现因为本地输入往往是手打的能和格式化字符串对上但 OJ 的测试数据可能不会像你想的那样。每次定义好 scanf 后回头扫一眼格式化串这也是刷题前的一个固定检查项目。9. 团队与协作学习找一个人互相“喂数据”一个人刷题容易陷入盲区自己的代码自己看不出问题。我实际体验下来最有效的方法是找一两个同学组队刷题互相给对方出测试数据。比如你写了一个排序题让对方构造一组包含负数、重复值、最大值的用例来跑你的代码往往一跑一个不吱声全是边界问题。这种方法本质上就是在模拟 OJ 的判题逻辑只不过由人来替你构造刁钻数据。谁更懂这道题的设计意图谁能造出更刁钻的数据谁对题目的理解就更深。东华OJ 这类系统本身不提供讨论区但自己组一个三五人的小群互相“找茬”进步速度会快很多。10. 刷题之外的三个建议10.1 保持代码风格一致缩进统一、花括号换行风格统一、变量命名有含义。代码不是只给 OJ 看的也是给你自己和同学看的。风格混乱的代码你自己 debug 的时候都会烦躁。长时间坚持整洁风格后面写项目时会非常受用。10.2 定期做总结而不是一直刷新题刷完 89-93 之后抽一个晚上做的事情应该是把五道题的思路在纸上画出来回忆每道题的坑在哪而不是立刻去刷 94。总结这五道题消耗的时间和你刷五道新题收获的成长是完全不一样的。前者是扎根后者是长叶。10.3 不要迷信题数要形成“解题框架”看到一道题你脑子里应该最先浮现的是这道题属于什么类型应该用什么套路。这个“看到题知道怎么想”的能力不会因为题刷得多就自动长出来需要你在每一道题的复盘里积累。比如看到“多组输入累加操作”脑海里自动弹出“记得重置累加变量”看到“两个数的最大公约数”自动想到“欧几里得算法”。这种条件反射就是刷题最宝贵的产物。在 89-93 这个区间刷完之后我最明显的感觉是做题之前会先停顿几秒想“考点”了。这个停顿就是进步。
返回列表