ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

东华OJ基础7-9题:C语言条件分支与循环的常见陷阱与自查指南

东华OJ基础7-9题:C语言条件分支与循环的常见陷阱与自查指南 如果你刚在东华OJ上把基础部分前几题刷刷刷地全过了一遍然后突然卡在第7题上连续提交几次都是WA先别怀疑自己这不是个例。我带过的班级里几乎每个学期都会上演同样的剧情基础前6题大家平均提交一两次就过从第7题往后提交次数突然飙升群里开始出现清一色的“大佬帮我看看这个哪里错了”截图。这倒不是第7题突然变难了多少而是因为东华OJ基础板块的题目节奏变了。前几题还在考“照着公式写”到第7题、第8题、第9题开始真正考“自己想逻辑”。这篇文章就围绕东华OJ基础7-9题展开把这一个阶段最常出现的三种题型、条件判断里的常见坑、循环题的底层思路以及OJ提交时那些看不见的潜规则一次讲清楚。正在学C语言、准备机考或者刚开始刷OJ的新手都可以照着这篇文章的思路去复现和自查。1. 为什么前六题顺风顺水到第7题突然开始WA先别急着怀疑自己先说结论这个现象太正常了几乎每个人都会在这里撞一次墙。东华OJ基础部分的题目大致是按顺序结构、选择结构、循环结构、数组、字符串这样一层层往上排的。前六题大多是“读入两个数、按公式算、输出”属于典型的照猫画虎阶段你只要能把老师上课讲过的语法例子看懂基本就是一遍过。真正让新手卡住的是第7题往后题面里开始出现“如果……否则……”“反复计算”这类描述。它不再满足于让你模仿语法而是开始要求你自己设计处理逻辑了。一道题拿到手先想条件怎么列、边界怎么处理再决定代码怎么写。这个思维转换才是多数人卡壳的原因。再加上OJ本身的判题方式也在推波助澜。记住一句话OJ是黑盒测试。后台会拿若干组预置的输入数据运行你的程序然后把你的输出和标准答案逐字符比对。它不关心你的代码写得是否优雅只看输出一不一样。1.1 OJ判题就是一场“盲人摸象”OJ没你想的那么玄乎。你提交一份源代码后台拿它编译成可执行程序然后用一组编好的输入数据去跑再把程序的输出和标准的输出逐字符比较。一致就AC不一致就WA程序跑超时了就是TLE运行过程中崩溃了就是RE。整个过程对你是完全黑盒的。这在实操中带来两个非常直接的结果结果是“逐字符”比的。多一个空格、少一个换行、字母大小写不一样都会当成错误答案处理哪怕你的计算结果完全正确。测试数据不止样例那几组。后台会准备很多边界数据最小输入、最大输入、负数、0、刚好卡在边界上的值等等。样例过了只能说明你走通了一条常规路径边界情况才是真正筛人的地方。这两条基本解释了一个很普遍的现象为什么本地运行样例明明全对一提交就红。你看到的“对”只是题目让你看到的那一部分“对”。1.2 不是语法难是思维开始换挡前6题基本是顺序结构读入、计算、输出三步走。到第7题开始出现“判断”“重复”很多同学第一反应是“有没有哪种函数能直接完成”其实根本没有也不需要。你需要做的只是把题目里的中文描述翻译成if、for和变量。我自己当年也有这个体会。后来养成了一个很简单的习惯拿到任何一道题先在草稿纸上写下三个词——读什么、算什么、输出什么。然后再往里面填分支和循环。思路顺了代码只是顺水推舟。这个方法比直接在代码编辑器里挤牙膏式地补代码高效得多后续几个章节的内容本质上也都是围绕这三个词展开的。2. 基础7-9题的高频题型数学计算、条件分支、循环累加东华OJ基础部分的第7题到第9题具体题面可能因学期、因老师配置的题库而不同但万变不离其宗翻来覆去基本是三种配方数学计算、条件分支、简单循环。你把这三类题吃透在考场上看到哪一道都能快速归类。2.1 数学计算类公式要熟精度不能踩雷第一种常见题型是数学计算。前面几题也有计算但那是“一个数加另一个数”闭着眼睛写。第7题开始计算开始带上小数、根号、平方、公式比如两点间距离、三角形面积、圆面积。拿“求两点间距离”举例题目描述大概是这样输入四个实数x1 y1 x2 y2输出两点间的距离保留两位小数。看起来是送分题但送分题也有坑。#include stdio.h #include math.h int main() { double x1, y1, x2, y2; while (scanf(%lf%lf%lf%lf, x1, y1, x2, y2) ! EOF) { double dx x1 - x2; double dy y1 - y2; double dist sqrt(dx * dx dy * dy); printf(%.2f\n, dist); } return 0; }第一个翻车点类型。坐标和距离都可能带小数用double别用float。原因很简单float有效精度大约7位两点距离这种计算要先算平方和再开根号中间过程误差会被放大结果很可能在第二位小数上出现分歧然后你输出了3.14标准答案是3.15WA。double有效精度15到16位基础题绰绰有余。第二个翻车点格式符。scanf读double必须写成%lf很多人顺手写%f轻则读到0重则直接崩。printf输出double保留两位写%.2f。平时统一记成“scanf用lfprintf用f”不容易出错。第三个翻车点输入个数。题目说“输入四个实数”有人用两个scanf分开读有人把逗号也读进去最后结果不对都不知道为什么。建议一个scanf一把梭严格按照题面描述的空格或换行来分隔。2.2 条件分支类边界条件比主逻辑更考人第二种题型是条件判断代表题是判断闰年、判断奇偶、输出两个数中较大的数、成绩分档。这些题目的主逻辑都是几行if真正坑人的是边界条件。以闰年判断为例闰年定义是能被4整除但不能被100整除或者能被400整除。经典错法有两种一是只写(y%40)把1900年判成闰年二是写了(y%40 y%100!0)却漏掉(y%4000)把2000年判成平年。if ((y % 4 0 y % 100 ! 0) || y % 400 0) { printf(YES\n); } else { printf(NO\n); }为什么这个判断看起来很绕因为公历修正规则本身就是分层的大部分能整除4就是闰年但要剔除整百年份剔除后又有一个特例400的倍数还是闰年。这种“先定主体规则再补特例”的思维在后面很多题里都会遇到闰年题只是最简单的载体。输出方面也要看清楚题面要求的是YES还是yes大小写不同就是WA。有些题甚至要求输出Leap Year或Common Year别凭印象乱写跟着题目走。2.3 简单循环类手动加不如交给循环第三种常见题型是简单循环典型题目是求1到n的和、求n的阶乘、统计某个范围内有多少个数满足条件。这里我直接用“求1到n的和”来演示#include stdio.h int main() { long long n, i; while (scanf(%lld, n) ! EOF) { long long sum 0; for (i 1; i n; i) { sum i; } printf(%lld\n, sum); } return 0; }这个代码里有两个容易被忽略的习惯。第一n和sum都用long long。很多人初学觉得int就够了但题面的数据范围可能给到10^9甚至更大int最大值才21亿左右1到n求和到10^9结果是5×10^17以上int一瞬间溢出输出负数或奇怪数字你还在怀疑人生其实是类型不够。第二sum0这行不能省也绝对不能放在while循环外面不归零。如果题目是多组输入每组开始前sum都要重置否则第二组数据会接着上一组的和累加输出越来越大。如果你对循环已经有一点感觉可以顺便想一想1到n求和的数学公式是n*(n1)/2那这个循环其实是可以省的。这个“能不用循环就不用循环”的意识到后面刷算法题时越来越重要。3. 条件分支题里比“会写if”更重要的事第2章把三种题型整体过了一遍接下来我把条件判断这个核心点单独拎出来因为它是最容易被新手低估、又最容易丢分的地方。3.1 独立if和else if连写结果是两码事先看一个特别典型的错误。假设题目要求把成绩分档90分及以上A80到89分B70到79分C60到69分D60以下E。有人这样写if (score 90) printf(A); if (score 80) printf(B); if (score 70) printf(C);你输入95分程序会一口气输出ABCD。为什么因为三个if彼此独立95满足第一个也满足第二个还满足第三个。每一个if都会做一次判断不会因为前面条件成立就自动跳过后面的if。要互斥必须用else if串起来或者用switch。这算是基础阶段最经典的“看起来没错但结果离谱”的错误之一比语法错更隐蔽因为编译器不报错输出还“挺工整”。正确做法是if (score 90) { printf(A); } else if (score 80) { printf(B); } else if (score 70) { printf(C); } else if (score 60) { printf(D); } else { printf(E); }用else if之后只要有一个分支命中后面的分支全部跳过。这种互斥结构的思路可以迁移到很多题里一个数要么正、要么负、要么零三段互斥如果你用三个独立if写在边界值上就会出现重复输出的问题。3.2 一个等号还是一个等号最容易看走眼的地方C语言里最冤的WA一定是这个if (a 1)而不是if (a 1)。前者是把1赋值给a赋值表达式的值就是1条件恒为真。你写了一百行代码最后卡在这个等号上找半天找不到因为它不报错结果就是不对。我自己的排查习惯是一旦某个条件分支该走的没走、不该走的走了先检查所有if括号里的符号看到单个等号十有八九就是它。顺带一提判断奇偶时还有一个和负数相关的细节。判断偶数可以直接写x%20但判断奇数时最好写x%2!0而不是x%21。原因是C语言里负数取模的结果可能是负数比如-3%2的结果是-1于是x%21在x-3时不成立奇数被漏判。基础题的数据范围一旦包含负数这种细节瞬间变成扣分点。3.3 浮点数比较和多条件里的组合判断第三个条件判断的坑来自浮点数。有些题目问“某点是否在某个圆上”按数学公式算完距离然后用距离是否等于半径来判断。问题是浮点数计算会产生微小误差两个看起来该相等的数在计算机里存的是0.30000000000000004和0.3直接比结果永远为假。遇到这类问题正确做法是比较两个数的差值绝对值是否足够小if (fabs(dist - r) 1e-9) { // 认为相等 }不过这是进阶话题基础7-9题里不常出现但遇到了就得知道不是“代码写错”而是“浮点数本来就不精确”。另外多条件组合里和||的短路求值也值得留个心。比如if (n ! 0 m / n 1)当n为0时左边为假右边根本不会执行程序不会因为除数为0崩溃。这看起来像“自动保护”其实只是短路求值的副产品但理解它之后条件顺序能写得更安全。我在实际刷题中还总结了一条排查链路遇到条件判断题WA按顺序检查分支之间是不是真的互斥有没有用else ifif括号里是单个等号还是双等号负数取模和浮点数比较有没有撞上输出格式的大小写、空格、换行是否和题面一致。按这条链路走一遍大部分让新手头疼的WA都能找到答案。4. 循环题的关键从“硬算”到“会算”循环题看着不复杂但真正写好需要把握几个点。这一章就把循环题的几个关键细节讲透。4.1 循环三要素和最容易丢的“最后一次”for循环有三个部分初始化、循环条件、更新。以for(i 1; i n; i)为例i1是起点in是终点i是步长。三个部分错了任何一个循环次数就会不对。最常见的“少一次”bug是把循环条件写成i n。求1到n的和时如果循环条件少了等号最后一项就没算进去。这个错误在样本数据比较特殊时特别有迷惑性n1时i初始是111不成立循环一次都不执行求和结果是0但正确答案是1你一眼能看出来可如果题目是“输入一个数n输出1到n之间所有奇数之和”当n2时正确结果是1而你循环少执行一次后恰好还是1这种“巧合”就会让你误以为代码是对的直到交了OJ才发现炸了。检查的时候别只看代码先在草稿纸上把循环执行几次、最后一次i是多少写出来这比盯着屏幕发呆高效得多。4.2 累加器与累乘器初始化错一步全盘皆输求和的累加器初始化为0求阶乘的累乘器初始化为1。原因很简单0加上任何数都不变1乘上任何数都不变。但人不是机器人容易在切换场景时忘了调初始值。我见过太多人第一题求和sum0第二题求阶乘还是sum0然后输出永远0。硬找代码逻辑找不出问题纯粹是“思维惯性”造成的。另一个高频错误在多组输入场景下sum定义在while外面每组输入进来后循环开始前忘记重新置0于是第二组、第三组的结果全部带上之前的历史值。这个错误有个非常迷惑的特征——第一组数据永远是对的后面的全是错的。如果你发现“第一个样例能过多组数据就乱”优先查累加器是不是没有在每组循环开始前归零。4.3 从循环到公式数据范围决定算法选择再往深一层基础循环题其实在帮你建立“复杂度意识”。求1到n的和用循环n10^6时瞬间算完n10^9时就要等一会儿n10^12时基本可以宣告超时。而等差公式n*(n1)/2无论n多大都是三次运算解决。所以拿到循环题第一件事不是急着写for而是看数据范围。那个范围数字会告诉你要不要用long long能不能写循环还是应该直接套公式。这个习惯越早养成越好因为后面数组、排序、字符串题复杂度直接决定你能不能AC。补充一个嵌套例子统计1到n之间能被3整除的数的个数。这个题只要一个循环加一个if但很能练习“在循环体内做条件判断”的组合结构int cnt 0; for (i 1; i n; i) { if (i % 3 0) { cnt; } } printf(%d\n, cnt);基础7-9题里如果出现“统计类”的题基本就是这个套路。先会这个后面数组题里的计数就是同一个思维换了个数据容器而已。5. 看不见的判题规则多组输入EOF与输出格式的坑很多时候你在OJ上WA不是因为算法错了而是因为踩了输入输出规则的坑。这一章讲的是最影响AC率、但教材里通常不细说的东西。5.1 单组输入和多组输入先判断清楚再动手OJ题面的输入部分经常有几种写法“输入一个整数n” —— 单组输入。“输入数据有多组每组占一行处理到文件结束” —— 多组输入。“输入包含多行每行一个整数0表示输入结束” —— 多组输入的特例。很多WA不是算错是没按题目要求的输入模式写。比如题目明明是多组输入你写了个只处理一组的程序样例恰好只有一组本地跑得完美提交后一碰到第二组数据程序已经退出后面的数据根本没处理。那怎么判断最稳妥的方法还是读题面题面永远是第一优先级。样例可以辅助判断样例输入有多行输出也有多行那大概率是多组。但最可靠的永远是里面的文字描述。多组输入的标准壳子while (scanf(%d, n) ! EOF) { // 对每组n进行处理 }scanf的返回值是成功读入的变量个数读不到东西时返回EOF。这个EOF全称End Of File可以理解成“已经读到文件末尾了”。你可以把它类比成看书读到最后一页的空白不是内容错了而是内容已经没了停在这里就好。5.2 EOF与“读到0结束”的标准写法有一种变体很常见题目说多组输入但以0作为结束。比如“每行一个正整数输入0表示结束”。这时就不能用裸的!EOF因为你要把0当作终止信号过滤掉while (scanf(%d, n) ! EOF n ! 0) { // 处理非0的n }这个写法利用了的短路求值如果scanf已经没有读到数据后面n!0根本不会执行不会出问题如果读到了n且n等于0循环结束0本身不参与处理。有些同学觉得“先读一个数然后while(n!0)再在里面读”也一样其实也对但上面的写法更简洁出错率更低。5.3 输出格式错了答案对了也白搭OJ判题是逐字符比对的这就导致输出格式成了很多人丢分的主要来源。常见要求有每组输出占一行保留几位小数用%.2f、%.3f控制输出内容大小写严格区分YES不等于Yes极端要求每两组输出之间空一行、最后一行没有多余空行。最后一类比较坑基础题不常出现真遇到可以在代码里加一个标志变量控制比如int first 1; while (scanf(%d, n) ! EOF) { if (!first) printf(\n); first 0; printf(%d\n, n * n); }这个“第一组前面不加空行后面每组前面先加空行”的技巧能保证行间空行正确同时最后不会多出一个空行。所以当你觉得“答案明明是对的”但OJ就是不给过先不要急着改算法老老实实把输出格式用样例逐字符对一遍包括空格、换行和大小写。这一步起码能排除一半以上的“假WA”。6. 提交前用这套顺序自查能省下很多次无效提交刷OJ到一定量之后你就会明白提交频率越高越容易被WA折磨。后来我给自己定了一个死规矩任何一道题提交之前都按下面这个顺序过一遍。6.1 一套我在提交前必做的七步自查把样例输入复制进程序输出和样例输出逐字符对比包括空格和换行手动测边界值最小的数、最大的数、0、负数、恰好卡在分界上的数如果是多组输入造两行以上输入模拟一次确认每组都能被处理检查数据类型int会不会溢出该不该用long long检查scanf和printf的格式符double读入是%lf输出保留位是%.2f全局搜索printf把所有临时调试输出删干净重读一遍题面重点看输入格式、输出格式、保留小数位、大小写要求。这套检查看起来啰嗦但它能帮你把无效提交从“靠运气”变成“靠流程”。我自己把流程跑顺之后WA数量明显下降。你不需要每条都背下来只要形成习惯十分钟内就能全部过完。6.2 常见报错的关键字都代表什么最后说一下OJ平台常见的几个结果理解了它们你看到那些英文缩写就不会慌CE编译错误代码没能编译成功。常见原因是缺头文件、漏分号、格式符写错。平台一般会给出编译器提示按提示修就行。WA答案错误输出和标准答案不一致。先查输出格式再查边界值然后查算法和条件逻辑。TLE运行超时程序在规定时间内没跑完。要么是死循环要么是算法太慢先检查循环条件再看数据范围是否需要换公式或换思路。RE运行时错误程序崩溃了。常见原因是除数为0、数组越界基础题里主要是除数为0检查一下有没有可能出现n0时做除法的场景。如果你看到的OJ结果是其他英文缩写对照这个表基本能定位问题方向。最让人抓狂的其实不是RE和CE而是WA——它只告诉你错了不告诉你错在哪。这时候千万别对着代码瞎改回到上一节的自查流程一步步排查才是正道。写到这里我想起自己当年第一次在东华OJ上刷到这一段的经历。前几题轻轻松松到第7题第一次卡住时我也一度怀疑“是不是我不适合写代码”。后来才发现问题根本不是智商而是我压根没养成“读题先看输入输出动手先列思路提交前先查边界”这三个习惯。这三道基础题的价值远不止那几条if和循环而是逼你在最低成本的题目上把这些习惯练出来。如果你现在正卡在这里不妨放慢一点把题面重读一遍把条件在纸上列一遍再回来写代码。大概率你会发现原来不是题难是之前自己写得太快了。
返回列表