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

资讯详情

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

OJ刷题入门:鸡兔同笼问题背后的输入输出与边界条件

OJ刷题入门:鸡兔同笼问题背后的输入输出与边界条件 做OJ刷题的人十有八九会对鸡兔同笼问题印象很深。我第一次在在线评测系统上做到OJ1004这道题时还觉得挺意外题目描述就一句话笼子里关着鸡和兔数头有n个数脚有m只问各几只。这不就是小学奥数题吗可真正以OJ题目的形式提交代码以后才发现数学上三秒得出答案不代表编码一次能通过。本文就借这道“相当于鸡兔同笼问题”的OJ1004把问题背后的数学模型、输入输出规范、边界条件处理、提交报错排查这些事完整捋一遍。适合刚接触OJ平台、还不熟悉判题规则的新手也适合准备各类在线笔试的求职者。1. 内容整体设计与思路拆解1.1 为什么说它“相当于鸡兔同笼”很多OJ题不会老老实实把“鸡兔同笼”四个字写在标题里而是换个外壳比如停车场里的自行车和汽车、笼子里的小鸡和小猪但拆开一看全是同一个二元一次方程组。以最原始的鸡兔同笼来说设鸡有x只兔子有y只头的总数是h脚的总数是f那么可以列出两个方程头的数量x y h脚的数量2x 4y f联立解方程组用第二个式子减去两倍的第一个式子2x 4y - 2(x y) f - 2h左边化简之后就是2y于是y (f - 2h) / 2也就是说兔子的数量等于“多出来的脚数的一半”。换一个更生活的说法假如笼子里全都是鸡那脚数应该是2h只现在实际有f只脚多出来的每一只脚都说明有一根“兔腿”没有被算对而每只兔子比鸡多2只脚所以兔子数就是(f-2h)/2。这个思路比死记公式好记得多遇到系数变化也更容易套。但问题是x和y是真实世界的数量必须是整数且不能为负数所以题目真正考察的是能不能把这些隐藏约束转化成代码里一个不漏的判断条件。大多数提交踩坑都踩在这个地方。1.2 这道题在OJ题库里的定位OJ1004这种编号常见于教学型的在线评测系统从编号就能看出来它属于“入门期必做”的题目。出题人把它安排在前1000多题的区间目的很明确让新手用最简单的题目熟悉OJ平台的完整流程——读题、设计输入输出、本地运行、提交代码、查看判题测试报告。很多人在这里就开始踩坑比如不知道要处理多组输入、不知道输出格式不能多一个空格、不知道类名必须写成Main。从算法角度来说这道题没有任何复杂度可言时间复杂度是O(1)、空间复杂度也是O(1)。但换个角度看它又是最考验基本功的题因为它逼着你理解平台规则。会写for循环不意味着你能在OJ平台上通过这道题这句话我见过太多反例了。1.3 不同OJ平台的同类题目差异我最初是在课程OJ上做的后来又陆续在华为OJ、西科大OJ平台、东华OJ上见过类似的变体。别看题目表面差不多平台的输入输出要求差异可能很大这里做一个对比平台输入风格输出格式注意事项华为OJ常见单组输入用空格分隔一行输出不能带无关注释隐藏用例较多特判严格西科大OJ平台部分题目为多组输入直到EOF每组结果换行需要处理多行输入东华OJ常见多组数据以特定标记结束结果之间留空行注意题目里的结束条件其他通用题库单组和多组都有大小写、下划线敏感一切以题目描述为准同样的解题逻辑换一个平台可能就因为“是否循环读入”而判成WA。这也是我建议每次提交前都重新看一遍输入输出描述的原因不能看到“相当于鸡兔同笼”就直接把以前某个平台的代码原封不动贴过去。2. 核心细节解析与实操要点2.1 判题系统的判定逻辑OJ判题系统的工作方式简单来说就是你提交源代码服务端用事先准备好的测试用例把你的代码编译、运行然后把程序输出和标准答案逐字符比对。比对的字母缩写大家都应该眼熟ACAccepted恭喜完全通过WAWrong Answer答案错误TLETime Limit Exceeded超时RERuntime Error运行时错误MLEMemory Limit Exceeded内存超限PEPresentation Error输出格式错误对OJ1004这种简单题来说最常见的卡壳是WA和PE。WA多半是边界条件或数学公式出错PE则往往是多打印了一个空格、少换了一行。很多教程只教你写代码不教你解读判题测试报告但报告其实是很好的反馈。比如它提示你在“脚数为奇数”这个用例上失败基本可以确定是忘了判断(f-2h)的奇偶性。2.2 输入输出格式的坑先说输入。鸡兔同笼题目常见的输入方式有三种单组输入一行两个整数多组输入读到EOF结束多组输入以特定终止标记结束比如0 0我见过很多新手写的代码是这样scanf(%d%d, h, f); printf(%d %d, chicken, rabbit);如果是单组测试题这没问题。但如果题目写的是“多组测试数据”你只读一组判题系统拿后面几组用例去跑你的程序直接结束了结果必然是WA。处理多组输入的标准姿势是C/Cwhile (scanf(%d %d, h, f) ! EOF)Javawhile (in.hasNextInt())Python用sys.stdin逐行读取再说输出。OJ的输出比较是逐字符进行的多一个空格、少一个换行都可能判错。鸡兔同笼的答案一般是先输出鸡的数量再输出兔的数量中间一个空格最后换行。如果题目要求无解时输出“No answer”那大小写和空格也要和题目描述完全一致写成了“No Answer”照样WA。2.3 边界条件的合法性判断这题的边界条件概括起来就是一个不等式组加上一个奇偶性判断。设头数为h、脚数为f脚数不能少于所有动物都只有2条腿的情况所以f必须大于等于2h脚数不能多于所有动物都有4条腿的情况所以f必须小于等于4h因为鸡有2条腿所以(f-2h)必须是偶数否则兔子数量不是整数上面三个条件都满足时兔子数(f-2h)/2自然非负鸡数h-兔子也自然非负举个例子头3个脚7只。按公式硬算兔子(7-6)/20.5不是整数这组数据实际上不可能存在。如果代码里不做取模判断直接当整数输出就会得到错误结果。所以核心判断可以写成if f 2 * h or f 4 * h or (f - 2 * h) % 2 ! 0: print(No answer)注意有些题目会明确写“测试数据保证有解”这种情况下你可以省略合法性判断直接算。但保险起见多写一层判断通常不会影响正确性也让你在遇到隐藏用例时更稳。3. 实操过程与核心环节实现3.1 C语言实现C是OJ上最常见、也最能暴露细节的语言。完整代码可以这样写#include stdio.h int main() { int h, f; while (scanf(%d %d, h, f) ! EOF) { int diff f - 2 * h; if (f 2 * h || f 4 * h || diff % 2 ! 0) { printf(No answer\n); continue; } int rabbit diff / 2; int chicken h - rabbit; printf(%d %d\n, chicken, rabbit); } return 0; }这里有两个细节值得说。第一while(scanf(...) ! EOF)可以同时兼容单组和多组输入。第二diff先算出来避免重复写表达式也更容易阅读。至于整型选择头和脚的数据范围一般不会太大int够用但如果你看到题目限定的头数范围很大比如超过10万就改用long long防止乘法和减法溢出。3.2 Java实现Java在OJ上的痛点主要是类名和Scanner性能。以华为OJ这样的平台为例提交的class名必须是Main类里面不能带package声明。代码可以写成这样import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner in new Scanner(System.in); while (in.hasNextInt()) { int h in.nextInt(); int f in.nextInt(); if (f 2 * h || f 4 * h || ((f - 2 * h) 1) 1) { System.out.println(No answer); continue; } int rabbit (f - 2 * h) / 2; int chicken h - rabbit; System.out.println(chicken rabbit); } } }这里用 1判断奇偶其实和% 2 ! 0完全等价只是很多老程序员习惯这么写。hasNextInt()和nextInt()配合天然支持多组输入。如果数据量非常大可以换成BufferedReader提速但鸡兔同笼这种题数据量一般很小Scanner完全够用。3.3 Python实现Python刷OJ输入输出一定要分清input()和sys.stdin。如果题目明确只有一组输入用input()没问题但如果要多组输入直到EOF直接写input()读到底部会抛EOFError所以最好用sys.stdinimport sys for line in sys.stdin: line line.strip() if not line: continue h, f map(int, line.split()) if f 2 * h or f 4 * h or (f - 2 * h) % 2 ! 0: print(No answer) continue rabbit (f - 2 * h) // 2 chicken h - rabbit print(chicken, rabbit)这里用整数除法//而不是/是因为Python的/会得到浮点数而OJ期望输出整数。line.strip()用来去掉可能的空行如果某一行是空行就直接跳过避免map(int, ...)在空字符串上报错。3.4 自测用例设计不管用什么语言提交之前都建议先在本地构造一组测试数据。我常用的用例是这样编号输入 h f期望输出说明13 82 1正常情况21 40 1全是兔子31 21 0全是鸡43 7No answer腿数是奇数53 5No answer脚太少小于2h62 9No answer脚太多超过4h70 00 0空笼子看题目是否允许第7个用例要看题目是否允许h0。如果题目保证h1这个用例就不必构造。把测试数据按行写到本地文件中喂给程序逐条对比输出能覆盖绝大多数边界条件再去OJ提交就不容易翻车。3.5 为什么我建议三种语言都写一遍这道题逻辑很简单但我仍然建议初学者把C、Java、Python各写一遍。原因不是凑代码量而是三种语言刚好覆盖三种典型输入风格C教你理解EOF和指针层面的底层处理Java逼你关注类名和Scanner的循环读取Python教你用流式读取避免EOF异常。同一个逻辑在不同语言下的写法差异恰恰是OJ刷题最需要适应的东西。以后你换平台做题语言不熟、输入模板不熟往往比题目本身更耗时间。4. 常见问题与排查技巧实录4.1 提交WA的高频原因WA绝对是最常见的反馈。我统计过自己带过的学生提交记录WA通常集中在下面几个原因边界条件漏判最常见的是忘了腿数为奇数这个无解场景。比如h3、f7按公式算兔子是0.5程序直接输出错误数值。公式系数套错题目把“脚”换成“轮子”自行车2轮、三轮车3轮如果还按2和4算结果必然不对。输出格式不符题目要求输出No answer你写成了No Answer题目要求先鸡后兔你输出成先兔后鸡。多组输入没处理只读一次就结束后续数据没有跑完。多余输出或注释某些老OJ对输出要求非常严格代码里出现中文输出或调试注释也可能导致WA。排查时不要急着改代码。先看判题测试报告显示的是哪类失败再对照上面清单逐项检查。大多数时候报告里已经暗示了失败用例的样子。4.2 读懂判题测试报告很多OJ会在提交结果显示“通过了X组测试中的Y组”点击还能看到详细报告。以我熟悉的课程OJ为例报告里通常会包含测试输入比如“头3脚7”你的输出和标准输出的diff判定的错误类型如果你看到标准输出是No answer而你输出的是1 1问题基本出在奇偶性判断。如果你看到所有输出内容都对但反馈是PE那就要检查每行末尾是否少换行、数字之间是否多空格。判题测试报告是定位问题的最直观材料比瞎猜代码高效得多。4.3 本地快速自测的小技巧最后分享一个我非常习惯的做法。本地创建一个input.txt把多组测试数据写进去3 8 1 4 1 2 3 7 3 5 2 9然后运行程序时把文件内容作为标准输入C程序编译后用./a.out input.txtJava用java Main input.txtPython用python main.py input.txt对比输出是否符合预期。这样做的好处是提交前你能肉眼确认多组输入、空行、大小写这些细节还能一次性回归所有边界用例。等代码稳定后再把这个文件清理掉不影响项目目录整洁。4.4 一个真实的翻车案例复盘有一回我在西科大OJ平台上帮同学看这道题他的逻辑一眼看去没问题兔子数、鸡数都算对了但提交就是WA。我把他的代码拿来跑本地自测用例全过。后来我干脆下载了判题测试报告发现失败用例是一个特别大的头数范围。再回头看代码他定义的是int h, f;计算4 * h时发生了整数溢出正确答案被截断成了负数边界判断自然全乱了。改成long long之后一次通过。这个案例给我的教训是别看题目简单数据范围描述一定不能跳过乘法溢出在简单的题里照样能要命。5. 这道题给我的练习体会刷题刷了很多年回头看OJ1004这种“相当于鸡兔同笼问题”的题反而觉得它最有教学价值。它小到不需要设计算法但又大到逼你把输入输出、判题规则、边界条件全部摸清。我在实际练习中感受最深的是很多人在简单题上花的时间其实不少只是没花在看得见的维度。代码的核心逻辑就三行真正决定能不能AC的是对细节的敬畏。从这道题之后我养成了几个固定习惯第一每道OJ题先读输入输出约束再动手写代码第二写完先用边界条件自测再提交第三遇到WA先拉判题测试报告不瞎猜。第一次提交就AC不是靠运气而是靠把可能的坑提前踩完。建议你也找自己学校或常用的OJ平台比如西科大OJ平台、东华OJ或者干脆去华为OJ上搜一道鸡兔同笼变体把整条流程走一遍。等你真正读懂一份判题测试报告里的隐藏用例OJ平台的判题逻辑在你眼里就不再是黑盒了。
返回列表