拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

DHUOJ基础题刷题笔记:循环、边界与OJ避坑指南

DHUOJ基础题刷题笔记:循环、边界与OJ避坑指南 最近在DHUOJ上刷基础题从第20题一路做到第30题。说实话前面十几道都是热热身真正让我停下来想了想的是基础25、26、27这三道。这三道题的难度不算高但特别适合拿来检验C语言或者Python基础里最核心的几块循环、分支、输入输出格式、边界条件。很多刚接触OJ的同学会在这种题上反复吃WA不是不会写而是掉进了各种格式和细节的坑里。这篇文章我不打算泛泛地讲“编程基础很重要”这种废话就围绕三道题把我实际做题时的思路、代码、踩过的坑、排查方法全部摊开讲。如果你正在刷DHUOJ或者任何其他OJ比如HDUOJ、POJ、洛谷这个系列的经验是通用的。看完之后你至少能搞明白一件事OJ的“基础题”到底在考什么以及怎么稳稳地把AC拿到手。1. OJ基础题的通用套路与DHUOJ的评测机制1.1 做题前先把评测环境摸清楚DHUOJ本质上就是一个Online Judge你提交源代码系统自动编译、运行拿你的输出和标准答案做比对。听起来简单但这套机制和平时在自己电脑上写代码的感觉完全不一样。本地跑通了不算数评测机跑通了才算数。我第一次用DHUOJ的时候用的C语言提交编译器选的GCC。后来我发现很多同学在OJ上连CE编译错误都遇到过好几回最常见的两个原因一是把C的语法写进了C文件里二是主函数写成了void main()某些编译器严格模式下直接不给过。老老实实写int main()最后return 0;这个习惯从一开始就要养成。还有一件事提交前必须看清题目告诉你用哪个语言。DHUOJ支持C、C、Java、Python这些但不同题目的时限和内存限制是固定的。基础题一般不卡时间但如果你用Python写特别复杂的循环碰到大数据量也可能TLE。所以基础阶段我建议优先用C/C练对底层的循环、变量、内存理解更扎实。另外一个新手最容易忽略的点OJ的输入输出是“严格匹配”的。多一个空格、少一个换行都可能让你从AC变成PEPresentation Error格式错误。DHUOJ对待PE通常就是判错不会给你放水。1.2 读题永远比写代码更值得花时间很多人的习惯是扫一眼题目、看完样例就开写。我自己也吃过亏。OJ的题面描述一般都很简洁但里面藏着几个关键信息输入范围、输出精度、多组测试还是单组测试、数据结束的标志是什么。比如输入范围决定你用什么数据类型。题目说n最大是10^6你开个int循环计算的时候可能就爆了还在那查半天WA。再比如输出要求“保留两位小数”结果你写成%f精度对不上又是一个WA。这些都是可以提前规避的。读题的时候我建议把三样东西圈出来输入格式、输出格式、数据范围。哪怕多花五分钟也比白白交三次WA强。还有一个基础题里特别常见的设定多组输入。有时候题目写“输入包含多组测试数据每组占一行处理到文件结束”这时候你的代码就得写成while (scanf(%d, n) ! EOF) { // 处理每一组 }很多新手只会写一次scanf结果只处理了第一组数据评测机上后面的数据全没跑直接WA。这种“EOF判读”的思路在OJ里几乎是必备技能25、26、27这几道题虽然没有特别为难你但后面一定会碰到最好从一开始就养成习惯。2. 第25题递推数列的循环功底2.1 题目场景与解题思路DHUOJ基础25这题我拿到手是这样的给定一个正整数n求一个递推数列的第n项。已知第一项是1从第二项开始每一项等于前一项加上一个和项数有关的数。这类题目在OJ基础题里几乎是标配考的就是循环和递推。举个例子假设规律是第k项等于第k-1项加2k-1。那么数列长这样1、4、9、16、25……眼尖的同学可能已经看出来了这其实就是n的平方。但如果题目直接告诉你求平方那就太没意思了。它非要包装成递推目的就是让你写循环。我的思路很简单从第1项开始用一个long long变量存当前项的值每次循环往里加增量。核心代码长这样#include stdio.h int main() { int n; scanf(%d, n); long long ans 1; for (int i 2; i n; i) { ans 2LL * i - 1; } printf(%lld\n, ans); return 0; }这里面有两个点特别值得说。第一为什么用long long不用int因为如果n跑到10^6第n项的值会远超int的范围。OJ特别喜欢在数据范围上挖坑你以为int够用结果中间计算爆了答案自然不对。基础题的教训之一就是凡是结果可能变大的一律先考虑long long。第二为什么是2LL * i - 1而不是2 * i - 1加上LL是为了让乘法以long long的精度进行避免int溢出后再赋给long long。虽然在这个式子里2 * i本身不太会爆但养成这个写法后面处理大数的时候能少踩很多坑。如果用的是Python版本就不用担心int溢出代码也更简洁n int(input()) ans 1 for i in range(2, n 1): ans 2 * i - 1 print(ans)2.2 易扣分的两个细节这道题的WA大户集中在两个地方。第一个是循环次数。有人写for (int i 2; i n; i)少了一次循环n1的时候可能碰巧对n3就开始错了。这种边界条件做题时必须自己验证一遍。建议每次写完代码先拿题目给的样例测再自己脑补n1、n2、n最大值的几组数据。第二个是变量初始化。有人把ans 1写到了循环里面结果每次循环都把答案重置了最后输出的永远是最后一次增量。这种问题本地编译不会报错跑起来结果莫名其妙只能靠经验避免。我的习惯是循环外面放“起点状态”循环里面只做“状态转移”这个思路在写DP的时候同样适用。3. 第26题打印菱形——循环嵌套的标准练习3.1 图形类题目的通用拆法DHUOJ基础26经典中的经典输入一个奇数n输出一个由星号组成的菱形。例如n5的时候输出* *** ***** *** *这种图形题考查的核心是循环嵌套和数学归纳。拿到手不要急着写先拆。菱形上下对称上半部分有(n1)/2行下半部分有(n-1)/2行。以n5为例上半部分3行下半部分2行。每一行由两部分组成前面的空格和后面的星号。拿上半部分第i行来说i从1开始空格数是(n1)/2 - i星号数是2*i - 1。n5时第1行空格2个、星号1个第2行空格1个、星号3个第3行空格0个、星号5个。下半部分其实就是上半部分倒过来从i (n-1)/2递减到1。思路理清了代码就很好写#include stdio.h int main() { int n; scanf(%d, n); int m (n 1) / 2; // 上半部分m行 for (int i 1; i m; i) { for (int j 1; j m - i; j) { printf( ); } for (int j 1; j 2 * i - 1; j) { printf(*); } printf(\n); } // 下半部分m-1行 for (int i m - 1; i 1; i--) { for (int j 1; j m - i; j) { printf( ); } for (int j 1; j 2 * i - 1; j) { printf(*); } printf(\n); } return 0; }说句实话这类题第一次写的时候很容易绕晕。我见过一个同学用了一个巨复杂的二维数组先把图形存下来再输出能跑对但完全没有必要。碰到图形题先找行号和空格/星号的数量关系再把公式写出来代码自然就顺了。3.2 输出格式的隐藏陷阱这道题我WA了两次才过原因说出来有点丢人第一版代码每行末尾多了个空格。从肉眼上看* 和*似乎没什么区别但OJ是按字符逐字节比对的多一个空格都不行。还有人在每行输出星号之后多打印了一个换行导致整个菱形中间多了一行空白也是PE。另一个隐藏比较深的坑是“行末换行”。有些人会想我每行末尾已经printf(\n)了如果整个图形前面、后面再空一行是不是也无所谓答案是不行。OJ要求你的输出和标准答案“完全一致”。所以写上return 0;之前建议在脑子里跑一遍第一行有没有多余的前导空格最后一行输出完有没有换行有时候题目要求最后一行也有换行有时候不要求看题。我也总结了一个小技巧遇到图形题先在草稿纸上写出n1、n3、n5三种情况的正确图形以此作为标准答案写完代码后逐一对比。这样能把大量格式错误提前拦截住。4. 第27题数据统计里藏着边界意识4.1 题目设定与常规解法DHUOJ基础27这次不是图形了是一道数据统计题。题目大概是输入一个正整数n再输入n个0到100之间的整数统计及格率、优秀率和不及格人数。及格线是60分优秀线是85分。第一眼看上去很简单无非就是循环读入、if判断、计数器累加。但想一次性AC还是有些细节要处理好。我的解法如下#include stdio.h int main() { int n, score; int pass 0, good 0, fail 0; scanf(%d, n); for (int i 0; i n; i) { scanf(%d, score); if (score 60) { pass; } if (score 85) { good; } if (score 60) { fail; } } printf(%.2f%% %.2f%% %d\n, 100.0 * pass / n, 100.0 * good / n, fail); return 0; }这里最关键的是那个100.0 * pass / n。很多人写成绩统计题时会写成pass / n * 100结果输出永远都是0.00。原因很简单整数除以整数结果还是整数pass / n在pass小于n的时候直接就是0再乘以100也是0。你必须在运算的一开始就把其中一个操作数转成浮点数100.0 * pass这一步就是在做这个事。4.2 边界条件与测试用例设计这道题更值得记笔记的地方是边界条件。n1的时候只有一个分数。如果它是95分及格率、优秀率都是100.00%不及格人数是0。这三个条件同时成立代码能不能正确处理if (score 60)和if (score 85)是两条独立的if不是else if这样才能让一个分数同时被统计进及格和优秀。如果你用了else if优秀人数就会被吃掉一大半最后算出来的率全错。再比如n1且这个分数是50分及格率0.00%优秀率0.00%不及格人数1。这种极端数据你平时可能不会注意但OJ的测试点里面一定有。所以每写完一道题我习惯在本地把这些极端边界跑一遍最小值n1分数边界0分、59分、60分、84分、85分、100分全及格、全不及格、全优秀这一套组合测下来基本能保证逻辑上没有漏判。还有一个格式问题百分号怎么打印。printf里想输出一个%必须写成%%。我见过有人直接在字符串里写了一个%结果编译报错或者输出乱码。printf(%.2f%%\n, rate);这个写法看起来有点怪异但它就是标准做法。5. 常见问题排查速查表与避坑技巧刷OJ和调试本地程序完全是两种节奏。本地写代码跑出结果你还能打断点、看变量OJ上你只有一个判定结果信息量很少。所以我整理了一张DHUOJ基础题的常见问题排查表都是我实际踩过或看别人踩过的坑。判定结果常见原因排查方法CE编译错误主函数返回类型不是int用了C语法但提交了C本地用gcc严格编译一次把warning也当error看WA答案错误算法逻辑不对数据类型溢出边界条件漏判拿极端样例自测把int换成long long试试PE格式错误多空格、少换行、行末有多余输出和题目给的输出样例逐字符比对特别注意行末空格TLE运行超时循环次数过多算法复杂度太高死循环检查循环退出条件看n的范围如果超过10^7就要优化RE运行错误数组越界除数为0递归栈溢出检查所有下标范围确认n不为0避免深递归针对这些排查我再说几个亲测有效的实操习惯。第一别用“题解对答案”式刷题。我一开始刷OJ的时候WA了就看别人代码看得懂但下次遇到还是错。后来改成“WA了先自己查半小时”实在不行才看提示效果完全不一样。基础题的价值不在于你AC了几道而在于你独立排查出了几个bug。第二多用assert或中间打印。但OJ上不要留打印语句否则会产生额外输出必然PE。本地调试的时候可以随便打印提交通道前记得注释掉。我自己常用的做法是创建一个“本地测试版”里面加上一些调试输出AC之后再提交干净版本。第三警惕浮点数输出精度。题目要求保留两位小数就老老实实用%.2f。别自作主张改成%.3f或%g评测系统是按固定格式比对的多一位小数字符都不一样了。6. 从三道基础题延伸到整个刷题习惯6.1 刷题后必做的“复盘三件事”三道题全部AC之后不要急着做下一题。我会回到每一道题问自己三个问题第一我的代码在最坏数据下能不能跑完比如n最大是10^6我的算法是O(n)还是O(n^2)如果是O(n^2)那评测机很可能TLE。第二我的代码能不能处理不规则输入比如输入中间夹着空行数据后面有多余空格scanf实际上会自动跳过空白字符但如果你是按行读字符串再手动解析就要小心这些情况。第三有没有更简洁的写法我做完26题之后发现有人用两段循环分别处理上半部分和下半部分也有人用一个对称的下标公式合并成一个循环。两种都能AC但前者更容易阅读和维护。OJ不考代码风格但你以后写的代码是要给人看的趁着基础题练习的时候养成清晰的习惯很划算。整理错题也是我很推荐的做法。不需要多精美一个备忘录就行记录题号、WA次数、WA的原因、最终怎么解决的。我刷完25、26、27之后翻了下记录发现自己的WA原因高度集中在“格式化输出”和“边界值漏判”上。意识到这个规律之后后面的题我写完就会先主动检查这两方面AC率真的提升了很多。6.2 给刚起步的人的一点实在建议DHUOJ基础25、26、27这三道题单独拿出来都不难。它们真正的价值在于逼你把“写代码”变成“写对代码”。在OJ上代码跑通不是终点正确、稳定、通过所有测试点才是终点。这种思维方式越早建立后面做算法题、参加比赛、甚至写工程项目都会受益。如果非要说一个最重要的心得我会选“边界意识”。写循环的时候想一下边界写输出的时候想一下格式写除法的时候想一下类型。很多WA都不是不会写而是这几个地方没想清楚。把这套意识带进后面的每一道题基础阶段就算真正过关了。最后再分享一个小习惯每次AC一道题之后去讨论区看看别人的代码不用多两三份就行你会发现同一个问题有人用三行解决有人用三十行解决那种差异本身就是特别好的学习素材。
返回列表