ARTICLE DETAIL

资讯详情

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

洛谷P5707:C语言时间计算与向上取整的经典入门题解析

洛谷P5707:C语言时间计算与向上取整的经典入门题解析 洛谷P5707【深基2.例12】上学迟到是我见过最能筛人的入门题之一。题面一句话就能看懂完整C语言代码写下来不到20行可它在洛谷入门题单里WA率一直不低很多新手反复提交都找不到原因。这道题真正考的并不是语法而是把一个生活场景拆成数学表达式再老老实实翻译成代码。下面就把“最晚几点起床”从读题到AC的完整链路拆给你看适合刚刷完循环和分支、想用一道综合题检验自己的C语言初学者。1. 先把题目“翻译”成人话它到底在考什么题目背景是这样的主角从家出发去学校家到学校的路程是 s 米他走路的速度是 v 米/分钟上课时间是早上8:00。这个人习惯提前10分钟到校。输入给出 s 和 v要求输出他最晚几点起床。这里有个点值得停下来想一下题目说的是“提前10分钟到校”不是“提前10分钟起床”。也就是说10分钟是算在路上的总耗时里的或者说到了学校之后要有10分钟缓冲。这一点理解错了后面整个公式都会偏。再看输入输出输入一行两个整数 s, v输出一行 HH:MM看到 HH:MM 就应该反应过来题目对输出格式有严格要求小时和分钟都必须两位不足两位补前导0比如 07:49而不是 7:49。1.1 题面与输入输出的真实含义把题面里的关键信息对应到代码里的变量是做题的第一步。我一般在草稿纸上列一张这样的对照表题面信息含义代码对应s 米路程输入整数 sv 米/分钟速度输入整数 v上课时间 8:00时间基准点8 × 60 480 分钟提前10分钟到校额外缓冲总耗时里再加10分钟HH:MM输出格式printf(%02d:%02d, h, m)把时间全部换算成分钟是这类时间题最通用的做法。为什么不直接用 8 小时去减因为后面涉及分钟和小时两个单位分开算很容易出现进位、借位错误比如“3点60分”这种根本不存在的时间格式。统一成分钟之后所有计算都在整数范围内完成最后再一次性拆成小时和分钟思路会清晰很多。1.2 为什么这道题是“入门但不简单”的典型坦白说这道题的语法难度几乎为零只需要会 scanf、printf、if、整数除法。但它把好几个最容易在入门阶段被忽略的数学边界集中到了一起。第一是除法方向。C语言的 / 对整数是向下取整而题目要的是向上取整这一点不搞清楚样例可能侥幸过换一组数据就WA。第二是减法方向。从8:00往前推如果耗时太长减出来可能是负数负数分钟怎么转成时间格式是这道题真正考验逻辑的地方。第三是格式化。很多新手代码逻辑全对最终挂在 7:09 输出成 7:9 这种格式问题上。这些坑单拎出来每个都很小但叠在一起就是一道经典的“入门杀”题目。2. 数学建模从“最晚几点起床”到一条公式2.1 先把时间统一成分钟先定义两个基础量上学路上耗时travel 路程 / 速度分钟向上取整总耗时total travel 10然后起床时刻就是wake 480 - total这里 480 就是早上8:00的分钟表示8 × 60 480。如果 wake 是正数说明当天起床如果 wake 是负数说明从8点往前推不够推得从前一天晚上开始算。举个例子帮助理解。s 1000v 100travel 1000 / 100 10total 10 10 20wake 480 - 20 460。460分钟 7小时40分钟也就是 07:40。这个结果很直观。但如果 s 480v 1travel 480total 490。480 - 490 -10这是一个负数。这个负10分钟代表什么代表从8:00倒退490分钟已经退到前一天晚上去了。所以必须加上24小时也就是1440分钟变成 1430再转成 23:50。这就是“跨天”处理。2.2 向上取整最容易漏掉的一环题目里有一个隐含条件容易被经验不足的人忽略速度和时间都是按分钟计算的但路程除以速度并不一定能整除。比如 10 米的路速度是 3 米/分钟需要 10 ÷ 3 ≈ 3.33 分钟。可代码里根本没有小数分钟的概念最小单位就是1分钟。你只走完了3分钟还剩 0.33 分钟的路没走完这0.33分钟也得算1分钟所以耗时是4分钟。这就是向上取整。C语言里 10 / 3 的结果是 3如果直接拿这个 3 去用就意味着少算了1分钟。在实际生活中只差1分钟可能没什么但在题目里少算1分钟就可能把起床时间算晚导致迟到所以这是致命错误。向上取整的原理可以理解成你约朋友8点见面路上要走3分零1秒你不可能3分钟就到答案是4分钟。只要还有1秒没走完就得按一整分钟算这就是题目里时间的颗粒度。那怎么在整数范围内做向上取整两个办法判断能否整除if (s % v 0) travel s / v; else travel s / v 1;公式法travel (s v - 1) / v;第二种方法很多人不理解。它的原理是整数除法向下取整如果想向上取整就在被除数上人为加一个“足够大但不至于影响整除部分”的数也就是 v - 1。只要原式有余数加上 v - 1 之后商就比原来多1如果刚好整除加上 v - 1 也不会让商发生变化。这个技巧在很多竞赛题里都很常用建议直接记住。2.3 跨天处理起床时间可能在前一天晚上wake 480 - totaltotal 如果超过480wake 就小于0。这时最晚起床时间其实落在前一天需要加上 1440 把它拉回 0 到 23:59 这个合法区间。新手常见的错误是直接输出负数比如算出 -10然后打印 -10 / 60 和 -10 % 60得到类似 -0:-10 的奇怪结果。正确做法一定是在输出前判断如果 wake 0就 wake 1440。严谨一点想如果 total 特别大wake 可能小于 -1440原则上应该用 while 循环反复加 1440直到结果非负。洛谷这道题的数据范围一般不会出现跨越两天以上的情况用 if 加一次就够了。不过我在代码里更推荐写 while逻辑更严谨也不会多花任何成本。3. 关键一步向上取整、时间换算与跨天处理的C语言实现3.1 向上取整的两种写法对比先比较两种写法在实际使用中的区别。写法一if / elseint travel; if (s % v 0) { travel s / v; } else { travel s / v 1; }写法二公式int travel (s v - 1) / v;从可读性来说写法一更直白新手能一眼看懂“不能整除就多算1分钟”。从简洁性和防错角度来说写法二更好少了两行分支也少了一种写错分支条件的可能。我个人推荐写法二但前提是你真的理解它为什么成立而不是死记公式。如果不理解建议先写写法一确保逻辑正确再尝试用公式替换。3.2 从公式到代码逐步翻译把前面推导的公式翻译成C语言核心流程只有四步读入 s 和 v算路程耗时 travel注意向上取整算 wake 480 - travel - 10负数加1440用 %02d 格式化输出小时和分钟。第3步里我用的是 480 - travel - 10逻辑上等价于 480 - (travel 10)。推荐写成 480 - travel - 10 这种展开形式因为一眼就能看出“上课时间480减去路程耗时再减去10分钟缓冲”。当然写成 480 - (travel 10) 也行看个人习惯。这里还有几个容易被忽略的小地方。输入用scanf(%d %d, s, v)就够竞赛环境下可以不检查返回值但如果你想写成更健壮的样子可以判断一下返回值是不是2不是就提前结束。变量命名也值得注意travel、total、wake 这种有明确含义的名字会让排查问题轻松很多别用 t、a 这种只有你自己看得懂的缩写。3.3 格式化输出为什么用 %02d到了输出这一步仍然有坑。要求是 HH:MM小时和分钟都占两位。7点49分要输出 07:49不是 7:49。C语言里%d不会自动补前导0写%02d才会如果数值不足两位左边补0如果超过两位照常输出。需要注意顺序先处理跨天再输出。有些新手先把 wake 转成小时和分钟再在负数状态下直接打印结果既没补0也没跨天整个输出完全错乱。正确流程是判断并修正 wake然后 h wake / 60m wake % 60最后 printf(%02d:%02d, h, m)。4. 边界条件与隐藏陷阱整除、补零、跨天、除零4.1 几个必测的输入用例把代码写完以后强烈建议先用下面这几个用例自测全过了再提交输入期望输出说明了什么1 107:49最短路径验证基础计算10 307:46验证向上取整10/3要按4分钟算9 307:47整除场景3分钟整不用1100 106:10路程较长但仍在当天480 123:50验证跨天总耗时超过480分钟第2个用例最关键。如果代码用了普通整数除法 10/3会算出13分钟总耗时输出 07:47但正确答案是14分钟总耗时输出 07:46。这一个用例就能筛掉所有忘记向上取整的写法。第5个用例用来验证跨天。s480v1 时走路要480分钟加10分钟缓冲一共490分钟8:00往前490分钟就是前一天23:50。如果你的代码看到 wake -10 就不知所措或者直接输出负数这个用例会把你打回原形。4.2 新手最常见的四类WA原因我在一些新手交流区观察下来这道题的WA基本集中在四类原因没用向上取整。直接用 s / v被带小数的情况坑了。忘了扣10分钟。“提前10分钟到校”这句话很多人把它当成废话计算结果比正确答案晚10分钟。跨天只加一次或者干脆不加。要么输出负数要么格式错乱。输出格式错误。没写 %02d输出 7:49 而不是 07:49。前两类是逻辑错误后两类是细节错误。但洛谷的评测不区分错误类型只要输出跟标准答案不一致就是WA。这也是为什么这道题看起来简单却总有新手反复提交失败的原因——四个坑踩任何一个都过不了。4.3 关于除零v0 时的处理思路严格来说这道题里速度 v 是正整数题目数据不会出现0。但“除数不能为0”是C语言里最基本的常识万一你拿着这道题的代码去改造成其他场景或者自己扩展测试碰到 v0 时s / v会直接触发运行时错误程序崩溃。如果想把代码写得防御性强一点可以在读入后加一个特判if (v 0) { // 速度为0题目不会允许这里可以按无法到达处理 printf(00:00\n); return 0; }不过竞赛环境下不需要这么写因为题目保证 v 0。我提这个主要是想说清楚整数除法的前提是分母不为0实际开发中这个检查很便宜值得养成习惯。4.4 数据范围与 long long 的取舍洛谷这道题的常规数据规模下int 类型足够。32位 int 最大约21亿而 s 和 v 通常是几万级别travel 和 wake 的中间计算也远不会溢出。但如果你刷到类似题目发现数据范围可能达到 10^9 甚至更大就要立刻想到用 long long。改起来也简单把变量类型换成 long longscanf 和 printf 的格式符换成 %lld处理思路完全不变#include stdio.h int main(void) { long long s, v; scanf(%lld %lld, s, v); long long travel (s v - 1) / v; long long wake 480 - travel - 10; while (wake 0) { wake 24 * 60; } printf(%02lld:%02lld\n, wake / 60, wake % 60); return 0; }养成“先瞄一眼数据范围再定类型”的习惯能帮你避掉一整类溢出WA。5. 完整代码与提交心得如何做到提交一次过5.1 可以直接提交的完整C语言代码综合前面的分析一份可以直接提交洛谷P5707的C语言代码如下#include stdio.h int main(void) { int s, v; scanf(%d %d, s, v); // 路程耗时向上取整 int travel (s v - 1) / v; // 总耗时 走路时间 提前到校的10分钟 int total travel 10; // 上课时间按分钟计是 8 * 60 480 int wake 480 - total; // 如果为负数说明要从前一天晚上开始算拉回24小时内 while (wake 0) { wake 24 * 60; } printf(%02d:%02d\n, wake / 60, wake % 60); return 0; }这份代码的核心就一行公式(s v - 1) / v配合一个 while 做跨天修正最后用%02d保证输出格式。整个程序没有任何高深语法却把整数运算、分支循环、格式控制全部覆盖到了。5.2 我在做题时坚持的三个习惯最后分享三个我自己的小习惯不仅是这道题刷所有洛谷入门题都适用。第一个习惯先手算样例再写代码。拿到题目先在草稿纸上把公式列出来用题目给的样例手算一遍确认结果与题目输出一致再动手。这样能避免“代码写完了才发现思路从一开始就错了”的浪费。第二个习惯提交前自定义三个边界用例。一个最小输入、一个整除边界、一个跨天用例。这道题就是 (1,1)、(9,3)、(480,1)三组数据一测四类WA原因当场就能发现。第三个习惯WA 之后先看数据别看题解。洛谷的评测会给出错误用例把错误用例带进本地程序跑一遍用 printf 把 travel、total、wake 中间值都打出来一般一眼就能定位问题。实在看不出来再上调试器。看题解是最后的手段因为自己排查一遍比看十次题解都记得牢。另外如果你偶尔遇到“提交失败无法解析路由对象”之类的提示先别慌。这是网站服务端的偶发问题和你的代码无关稍后重新提交就好不要在本地反复改代码浪费时间。5.3 一个值得做的扩展练习最后再啰嗦一句这道题的价值不在代码量而在它强迫你想清楚“整数除法的方向”“跨天要怎么折算”“输出格式如何保证”这三件事。想明白这三个点后面再遇到日期时间类的题比如更复杂的时钟问题、日历问题思路都会顺很多。你可以自己把题目改一版练手如果上学时间是 8:10或者要求提前15分钟到校公式该怎么调整如果输入给的是起床时间让你判断会不会迟到反过来怎么比较想通这些扩展P5707 才算真正刷透了。
返回列表