资讯动态

洛谷P5707上学迟到:C语言时间计算与向上取整的细节

发布时间:2026/10/5 4:34:51 来源:尧图企业网站定制
在洛谷上刷题有一类题目让很多新手又爱又恨——题目一看就懂样例一跑就过提交上去却一片WA。P5707【深基2.例12】上学迟到就是这样一道题。表面上是简单的“路程除以速度算时间”实际往里一挖向上取整、跨天回退、补零输出三个坑一个不少。这篇文章就把这道题从读题到AC的完整过程拆开讲一遍顺便聊聊C语言解题时最容易犯的细节问题以及我在实际调试中踩过的坑。这道题的适用人群非常明确刚开始学C语言、准备在洛谷刷基础题的新手以及想通过一道小题把“整数运算”“取整”“格式化输出”这些基础概念彻底搞懂的朋友。题目本身不复杂但很能检验你有没有真正理解计算机里整数除法的行为以及能不能把一个生活中的时间问题转换成清晰的数学表达式。1. 先看懂题目在说什么P5707 的上学场景拆解1.1 题目到底让你算什么题目构建了一个非常生活化的场景一个学生每天上学从家到学校的路程是 s 米他走路或者骑车的速度是 v 米/分钟。每天起床之后还要花 10 分钟洗漱。学校要求最晚 8:00 到校问这个学生最晚几点起床才能保证不迟到。我把这类问题叫作“时间反推题”。正常我们习惯的是“已知出发时间求到达时间”只要用出发时间加上路上花费的时间就行。但这道题反过来它给定了“必须到达的时间”要你倒推出“最晚出发时间”。很多新手在这里容易懵不是因为计算难而是因为思维方向没转过来。其实转换方法很简单把“8:00到校”当成一个最终期限用它减去所有需要花费的时间剩下的那个时刻就是最晚的起床时刻。公式写出来是这样最晚起床时刻 8:00 - 洗漱10分钟 - 路上花费时间这个公式是所有后续推导的核心。只要你能把这句话用数学表达式写清楚这道题就成功了一半。1.2 题目的两个隐藏考点光有上面的公式还不够这道题之所以被放在“深基2”的例题里是因为它在基础公式之上藏了两个非常经典的考点这也是为什么很多人样例能过、提交却WA的原因。第一个考点是向上取整。路程 s 和速度 v 都是整数但 s 除以 v 的结果不一定是整数。比如 s100 米v30 米/分钟100÷303.333...分钟。现实中你不可能走 3.333 分钟就精确到达实际上你得走满 4 分钟才能走完这 100 米因为最后那 0.333 分钟的路程你也得走完。所以路上花费的时间必须向上取整。第二个考点是跨天回退。如果路上时间加上洗漱时间特别长比如超过 8 小时那么最晚起床时间就不是当天的某个时刻而是前一天晚上了。这时候题目要求你仍然输出一个 24 小时制的时间比如 23:58代表的其实是前一天晚上 11 点 58 分起床。这个“时间跨越午夜”的处理是很多新手第一次接触最容易在这里翻车。另外还有第三个不算考点但要命的细节输出格式必须是HH:MM小时和分钟都要补前导零。比如 7 点 5 分要输出07:05不能输出7:5。这个细节单独看很简单但和前面两个坑叠在一起就很容易出错。2. 思路推演把“时间反推”变成“分钟算术”2.1 为什么选“分钟”作为唯一时间单位处理时间类题目我个人的习惯是先把所有时间换算成同一种最小单位算完之后再转换回可读的时间格式。在这道题里最小单位就是分钟。为什么不直接用“小时 分钟”来计算呢因为涉及到借位。比如 8:00 减去 25 分钟很多人能算出 7:35但如果减的是 70 分钟呢8:00 减 70 分钟是多少7:00 减 10 分钟那就是 6:50不对8:00 往前 70 分钟应该是 6:50这个心算还算能接受但如果是 480 分钟、950 分钟呢心算就很容易出错。更麻烦的是还要处理跨天。如果结果跑到前一天你不仅要从 8:00 往前减还要考虑“往前一天”意味着什么小时和分钟之间的借位关系会变得非常混乱。所以最好的做法是把 8:00 表示成分钟数。一天有 1440 分钟从 0 点开始算8:00 就是第 480 分钟8 × 60 480。洗漱 10 分钟就是 10路上时间是路程除以速度的向上取整。用 480 减去所有耗时得到的是一个整数分钟数。如果这个数大于等于 0说明是当天如果小于 0说明是前一天加上 1440 就回退到前一天的对应时刻。这个思路的本质是把时间看作一根从 0 到 1440 的数轴跨天就是“绕回”数轴的起点类似于时钟走到 12 点后会回到 0 点。用生活化的类比来说就像你凌晨 1 点睡觉前看表表显示的是前一天晚上 11 点而不是“负 1 点”。计算机里的时间运算也是这样超过 24 点就归零重来。2.2 向上取整的 C 语言写法别急着用浮点数很多新手一看“向上取整”第一反应是引入math.h用ceil()函数#include math.h int travel (int)ceil((double)s / v);这段代码在数学上没错但它把问题复杂化了。用浮点数会有精度问题而且还要做两次类型转换代码啰嗦对于基础题来说完全没有必要。C 语言的整数除法有一个特性两个整数相除结果只保留整数部分小数部分直接舍弃相当于向下取整对于正数来说是截断。比如 7 / 2 3不是 3.5100 / 30 3不是 3.333。那么问题来了怎么用整数运算实现“向上取整”这里有个非常经典的技巧。设两个正整数为 a 和 b要计算 a/b 的向上取整可以这样写(a b - 1) / b为什么成立我来推导一下。如果 a 能被 b 整除比如 a90b30那么 (90 30 - 1) / 30 119 / 30 3整数除法119÷303.966取整为3结果和 90/30 一样都是 3。如果 a 不能被 b 整除比如 a100b30(100 30 - 1) / 30 129 / 30 4129÷304.3取整为4正好是 100/30 向上取整的结果。这个式子背后的逻辑是加上 b-1 之后只要 a/b 有余数哪怕余数是 1分子也会超过 b 的整数倍从而让整数除法的结果向前进位如果刚好整除b-1 不会改变整数部分结果保持不变。这是一道非常经典的整数运算技巧在很多算法题里都会用到建议直接背下来但更重要的是理解原理。在这道题里路上花费的时间就是int travel (s v - 1) / v;注意前提是 s 和 v 都是正整数。如果题目可能出现 0 或负数这个公式就不适用了需要额外判断。但这道题的路程和速度显然都是正数所以可以放心用。2.3 跨天问题的核心判断起床时间小于 0 怎么办算完总耗时之后最晚起床时间就是int wake 8 * 60 - 10 - travel;这里8 * 60就是 480 分钟。如果wake 0说明这个时刻还在当天范围内直接转成小时和分钟输出即可。如果wake 0说明最晚起床时间已经跑到前一天了。比如 wake -1表示“8:00 往前推 481 分钟”也就是前一天晚上 23:59。那么我给 wake 加上 1440一天的分钟数把它拉回 0~1439 的范围内if (wake 0) { wake 1440; }这里有一个容易被忽略的问题加一次 1440 够吗如果总耗时超过 1440 分钟wake 加一次 1440 之后可能还是负数。碰到这种情况严格的写法应该是用循环while (wake 0) { wake 1440; }不过按这道题的实际场景和常规数据范围上学路程一般不会夸张到走好几天所以用一次if判断就够了。但如果你在写通用代码或者想彻底避免这种边界问题用while更稳妥。我个人在比赛里一般写成while多写三个字母换一份安心不亏。3. 完整 C 语言代码与逐段讲解3.1 完整代码下面给出我通过这道题时使用的完整代码加上必要的注释方便对照理解#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; // 最晚起床时间用分钟表示从0点开始计算 int wake 8 * 60 - total; // 如果起床时间在0点之前说明是前一天回退一天 if (wake 0) { wake 1440; } int hh wake / 60; // 小时 int mm wake % 60; // 分钟 printf(%02d:%02d\n, hh, mm); return 0; }这是我第一次提交就通过的版本。结构非常简单没有花哨的语法但每一步都对应一个明确的数学逻辑。3.2 逐段讲解与输出格式细节代码第一段是输入int s, v; scanf(%d %d, s, v);这里有个基础但重要的点scanf里面一定要写取地址符。很多新手在这个地方丢分编译不报错但运行起来数据根本没有正确读入输出结果就会很奇怪。你可以写scanf(%d%d, s, v)去掉中间的空格不影响功能但保留空格更符合常规书写习惯也方便阅读。然后是向上取整的核心行int travel (s v - 1) / v;注意这里的变量类型。s 和 v 都是int整个表达式全是整数运算结果是整数。这里有一个我实际踩过的坑如果你写作(s v - 1.0) / v把分子变成浮点数那么整个表达式会变成浮点数除法结果可能是 3.0、4.3 之类的值然后赋值给int类型的 travel 时会发生截断。截断和向上取整在某些情况下会得到错误结果非常容易翻车。所以请保持分子分母都是整数让 C 语言做整数除法。接下来是时间反推int wake 8 * 60 - total;这里的8 * 60可以写成480我建议写8 * 60因为可读性更好能让人一眼看出这是在表示“8点”。写代码不只是给机器看的更是给人看的。然后是跨天判断if (wake 0) { wake 1440; }这里 1440 就是24 * 60。我同样建议写24 * 60而不是直接写1440原因同上可读性更好。有时候在代码评审里直接写 1440 会被认为是用魔法数字不够优雅。最后是输出printf(%02d:%02d\n, hh, mm);%02d是这道题的另一个考点。它表示输出一个整数占至少 2 位宽度不足 2 位时用 0 补位。所以 7 会输出为075 输出为05而 12 就正常输出12。如果你用的是%2d那不足 2 位时是用空格补齐输出7前面带一个空格这是不对的。如果你用%d输出7:5更是错的。这里用的是0标志位和-左对齐一样是 printf 格式控制里非常基础但特别常用的修饰符。我还想多提一句如果你在写代码的时候暂时忘了%02d这种写法也可以手动补零if (hh 10) printf(0%d:, hh); else printf(%d:, hh); if (mm 10) printf(0%d\n, mm); else printf(%d\n, mm);这样写功能上完全正确但明显不如%02d简洁。所以建议把这个格式控制符记牢以后做任何时间日期类题目都会用到。4. 现场调试记录那些年我在这道题上交过的 WA4.1 最容易翻车的三个点我第一次做这道题的时候样例跑通之后得意洋洋地提交结果 WA 得莫名其妙。后来反复排查发现是一个很隐蔽的问题我把向上取整写成了ceil(s / v)但s / v在 C 语言里先做了整数除法得到的是向下取整的结果然后ceil对一个整数根本没作用。也就是说我实际上用了向下取整而不是向上取整。这个坑很经典。很多初学者会把数学里的公式照搬到代码里却忽略了 C 语言整数运算的特性。正确的做法是用(s v - 1) / v或者老老实实写成(int)ceil((double)s / v)但后者要引入浮点运算不推荐。第二个容易翻车的点是跨天判断写反了方向。有人会写成if (wake 0) { wake 1440; }这个错误是把“起床时间在 0 点之后”当成了需要回退的情况结果输出完全错乱。所以写代码之前一定先把逻辑捋清楚只有 wake 小于 0也就是起床时间跑到前一天了才需要加 1440 拉回当天的表示范围。第三个翻车点是小时分钟换算。有人会这样写int hh wake % 60; int mm wake / 60;把小时和分钟搞反了。这样输出出来的时间会非常离谱。我的检查技巧是最后输出之前用自己算好的几组测试数据手算一遍确认wake / 60是小时、wake % 60是分钟。这个顺序和数学上“除以60取商得小时、取余得分钟”的直觉一致多验证几次就能形成肌肉记忆。4.2 手写测试用例与边界验证做题只跑样例是远远不够的我会习惯性地构造几个边界数据来验证代码。第一组最常规的情况走 50 米速度 10 米/分钟。路上时间 5 分钟加洗漱 10 分钟总共 15 分钟最晚 7:45 起床。输入50 10代码计算travel (50 10 - 1) / 10 59 / 10 5total 5 10 15wake 480 - 15 465hh 465 / 60 7mm 465 % 60 45输出07:45正确。第二组测试向上取整走 100 米速度 30 米/分钟。100 / 30 是 3.333向上取整是 4。输入100 30travel (100 30 - 1) / 30 129 / 30 4total 4 10 14wake 480 - 14 466hh 466 / 60 7mm 466 % 60 46输出07:46。这里有个细微点如果错误地用了向下取整会算出 travel3输出07:47但实际 7:47 起床出门8:00 前走不完 100 米会迟到所以答案是错的。第三组测试跨天走 9999 米速度 10 米/分钟。travel 1000 分钟向上取整total 1010wake 480 - 1010 -530小于 0加 1440 得 910hh 15mm 10输出15:10。这个结果的含义是前一天下午 3:10 起床洗漱 10 分钟再走 1000 分钟才能赶在 8:00 前到校。很夸张但逻辑正确。这种“构造极端数据跑一遍”的习惯是我做任何算法题都会做的。你不需要在每次做题时都测这么多组但一定要养成至少测一组边界数据的习惯尤其是这种存在向上取整和跨天逻辑的题边界最容易暴露问题。5. 常见问题与排查技巧速查5.1 提交报错、输出不对怎么查我在带新手刷题的过程中总结了一些常见问题的排查方向这里以速查表的形式整理出来症状可能原因排查与修复方法编译报错提示 undefined reference toceil算向上取整时用了ceil()但没引入math.h或者链接时没加-lm改用(s v - 1) / v整数写法直接避免浮点函数样例能过但提交 WA向上取整写成了向下取整比如s / v后直接使用检查travel的计算表达式确认用了(s v - 1) / v输出时间是负数跨天判断缺失wake 为负数就直接除以60在换算小时分钟前先判断wake 0并加 1440输出8:5而不是08:05没有用%02d补零改为printf(%02d:%02d\n, hh, mm)输出8: 5前面是空格用了%2d而不是%02d%2d用空格补位%02d用0补位记住这个区别输入数据读不进去输出乱码scanf漏写检查scanf(%d %d, s, v)是否写成了s, v时间始终不对差一小时左右小时分钟换算搞反检查hh wake / 60; mm wake % 60;的顺序还有一个很多人会忽略的问题变量类型。如果题目数据范围变大比如 s 和 v 可能超过int的范围那么s v - 1可能溢出。虽然在 P5707 这道题里数据范围不会那么大但我在做其他题目时养成了一个习惯凡是涉及加法、乘法的中间结果先估算一下会不会超过int的范围必要时用long long。这个习惯能帮你避开很多隐蔽的坑。5.2 一点做题习惯的碎碎念这道题让我想起一个很普遍的现象很多新手做题喜欢“猜代码”看到一个题就开始写写完直接交WA 了再改改了再交完全不顾自己到底在算什么。这样不仅效率低而且永远无法真正提高。我推荐的做法是“先手算再写代码”。拿到 P5707 这样的题先在草稿纸上把公式列清楚路上时间 路程 ÷ 速度向上取整总耗时 路上时间 洗漱10分钟起床分钟数 480 - 总耗时如果起床分钟数小于0加1440把分钟数转成 HH:MM 格式输出这样整个过程是透明的每一个变量都有明确的物理意义写代码的时候就只是“翻译”你的思路而已。错误往往出在思路不清的时候而不是代码本身。我还想强调一下格式化输出的习惯。在很多编程环境里printf的输出格式是严格判定的多余的空格、换行、缺失的前导零都会导致 WA。所以在提交之前最好确认一下题目输出的格式要求尤其是时间日期这种对格式敏感的内容。最后再分享一个小技巧这道题虽然简单但它涉及的时间处理思路在以后的算法题里会反复出现。比如“给定一个截止时间求最晚开始时间”“两种计时系统之间的换算”“时间跨天/跨月/跨年的处理”本质上都是在做同一件事把时间统一成最小单位线性地加减最后再转换回人类可读的格式。我个人的体会是入门阶段的题目不要只看 AC 率更要看这道题能帮你巩固哪些基础概念。P5707 帮助我彻底搞懂了 C 语言的整数除法行为、向上取整的整数实现、printf 的格式化控制符还有“边界条件要提前想清楚”的做题习惯。这些知识点单独拎出来都很小但组合在一起就是编程基本功的底色。如果你正在刷洛谷的基础题建议多花几分钟把这道题的每个细节吃透比急着冲难题有用得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑