第一次刷蓝桥杯基础练习里的“龟兔赛跑预测”时我一度觉得这题简单到不需要写代码——兔子速度快领先够了就睡乌龟一直在跑套个追及问题公式不就行了结果提交后不是超时就是答案错误折腾了大半天才反应过来这道C模拟题真正的考点从来不是物理公式而是你能否把一个时时刻刻在变化的比赛过程老老实实翻译成循环逻辑。对于准备蓝桥杯的初学者来说它是最好的“模拟题入门教材”之一麻雀虽小但状态切换、边界判断、时间推进这些要素全部齐活。这篇文章我会从题目条件拆解讲起带你过一遍状态机建模的思路、每秒推进一步的模拟写法最后给出完整的AC代码和自测数据。不管你是第一次接触这道题还是已经写了几个版本却在某个测试点反复翻车按着这篇文章的思路走一遍应该都能理清楚。1. 题目里的每个条件都是程序里的一个变量先明确题目的真实面目。原题输入一行五个整数v1兔子速度米/秒、v2乌龟速度米/秒、t兔子领先多少米时触发休息、s兔子休息多少秒、l赛道长度米。输出一行先是胜者标识R代表兔子T代表乌龟D代表平局再换行输出胜者到达终点的时间秒。这里要特别提醒一个最容易先入为主的坑不要看到字母t就以为是“兔子跑了多久”。在这个题里t是“领先阈值”不是“跑步时长”。范围上也别想太多用int就够赛程和速度都在普通整数范围内。1.1 输入输出的真实含义把五个数值之间的关系理清楚程序结构才会清晰。比赛从0秒开始乌龟是永动机每一秒都向前推进v2米没有任何条件能让它停。兔子则是一个“有脾气”的运动员它跑得比乌龟快但只要发现自己领先乌龟至少t米就会立刻停下来休息s秒。输出端要注意如果兔子先到终点输出R乌龟先到输出T两者同时达到终点输出D。时间统一用“秒”作为单位整数输出不要搞小数比赛中这样设计就是为了方便模拟。1.2 隐藏条件兔子睡醒后可能继续睡真正容易漏掉的是原题描述里的这句话兔子休息结束后如果发现自己领先乌龟的距离仍然大于等于t它会继续休息而不是立刻奔跑。很多初版代码在这里栽跟头——兔子明明领先一大截睡了一觉醒来发现还是领先按错误逻辑它应该跑结果跑一秒后又触发休息整个流程就乱了。还有人的处理方式是把“继续休息”写成嵌套循环一层套一层最后要么死循环要么漏判条件。这种“条件驱动”的行为用方程硬解很难一次写对但用状态机建模就会非常自然兔子只关心自己当前是“跑”还是“睡”以及距离差是否跨过阈值。2. 状态机视角兔子只有“跑”和“睡”两种状态模拟题的核心是状态管理。对于这道题乌龟没有状态它是一个匀速移动的质点每秒固定加v2兔子有两个状态跑步中、休息中。状态之间的切换条件非常明确跑步中且当前领先距离 t则切换到休息状态持续s秒休息中剩余休息时间耗尽则切换回跑步状态。用状态机的眼光看问题你就会发现变量的设计其实很自然。我只需要一个int变量restTime就能同时表达兔子的状态和剩余时长restTime 0表示兔子正在休息数值就是还要休息多少秒restTime 0表示兔子处于清醒状态可以跑。2.1 为什么不需要单独的“睡眠标记”你可能第一反应是要不要定义一个bool isSleeping来标记兔子在睡其实不用因为isSleeping和restTime 0完全等价。用一个int代替bool加计时器反而让代码更紧凑也不容易出现“标记改了但计时器忘改”的连锁bug。这就像闹钟restTime就是闹钟上剩余的倒计时大于0就是还在睡等于0就是醒了。睡没睡看一眼剩余时间就知道不需要额外再贴一张“我睡了”的纸条。2.2 距离差的判断时机比赛是否触发兔子休息取决于“兔子距离减乌龟距离”的实时值。为了判断这个差值我用两个变量distR兔子当前已经跑过的距离distTurtle乌龟当前已经跑过的距离。比赛从0位移出发distR和distTurtle都初始化为0。每过一秒乌龟距离固定加v2兔子则根据restTime决定这一秒加不加v1。当distR - distTurtle t时兔子就该准备进入休息状态了。这个差值会在兔子跑步的过程中不断变化兔子跑时差值增大兔子休息时差值缩小。整个程序的节奏就是每隔一秒刷新这两个距离然后判断要不要切换兔子的状态。3. 每秒推进一步的循环慢镜头回放模拟题通常有两种推进粒度按事件推进或者按固定时间步推进。对于龟兔赛跑我强烈推荐每秒推进一次原因很简单状态切换条件是“当前距离差 t”而这个差值每一秒都在变按秒推进最容易保证判断不出错。采用事件推进的人可能会想反正兔子休息s秒乌龟跑s*v2米不如一次算出休息后的位置话是没错但一旦兔子睡醒后发现还领先又继续睡这种一次性计算就需要再包一层逻辑。更麻烦的是如果乌龟在兔子睡觉期间已经越过终点线你还要单独判断“是否在睡眠中途结束比赛”边界处理很容易漏。3.1 推荐推进顺序先跑乌龟再管兔子每轮循环内部的执行顺序直接决定逻辑是否清晰。我采用的顺序是乌龟先跑一秒distTurtle v2再处理兔子若restTime 0则兔子休息restTime--否则兔子跑一秒distR v1跑完后检查差值是否达到t检查比赛是否结束time加1。先处理乌龟再处理兔子是为了让两个距离的更新都基于“当前这一秒”避免出现“兔子先跑乌龟却还用上一秒距离来比较”的错位。兔子休息时乌龟照样在跑这与现实完全吻合。3.2 前十秒的变量变化表格理论背得再多不如看一张变量变化表。我用经典的样例数据来演示v1 10v2 5t 50s 60l 1000。表格里记录每秒结束时各个变量的值时间/s乌龟距离兔子距离距离差restTime兔子动作151050在跑21020100在跑31530150在跑42040200在跑52550250在跑63060300在跑73570350在跑84080400在跑94590450在跑10501005060触发休息看到第10秒兔子跑完后距离差恰好等于50达到了阈值t于是restTime被设为60。从第11秒开始直到第70秒兔子都会处于休息状态每秒restTime递减而乌龟则一直在累积距离。这个表格建议你亲手在纸上写一遍模拟题对“变量随时间的演变轨迹”有感觉之后写代码就不容易乱。4. 完整AC代码以及一个更精确的改良版直接上核心代码。这是我推荐的主流写法逻辑简洁适合比赛场景。#include iostream using namespace std; int main() { int v1, v2, t, s, l; cin v1 v2 t s l; int distR 0; // 兔子跑过的距离 int distTurtle 0; // 乌龟跑过的距离 int time 0; // 当前比赛时间 int restTime 0; // 兔子还需要休息的秒数0表示清醒 while (distR l distTurtle l) { // 乌龟每一秒都在跑 distTurtle v2; if (restTime 0) { // 兔子还在休息这一秒不跑 restTime--; } else { // 兔子清醒跑一秒 distR v1; // 跑完这一秒检查是否领先达到阈值 if (distR - distTurtle t) { restTime s; } } time; } // 循环结束说明至少有一方到达终点 if (distR l distTurtle l) { cout D endl; cout time endl; } else if (distR l) { cout R endl; cout time endl; } else { cout T endl; cout time endl; } return 0; }这段代码的循环条件我直接写成distR l distTurtle l好处是比赛结束条件被放在while入口不需要在循环体内部用break。只要有人越线循环就退出干净利落。4.1 代码里最容易被忽略的一行注意第19行distR v1这行只在兔子清醒时执行。很多新手会把兔子跑步写成无条件执行兔子每秒都跑v1再在休息时减掉距离——这显然会超时或者算错。我们的做法是让restTime来决定这行代码跑不跑而不是让距离在之后被“补偿式”扣掉。另一个关键点是time的位置。time必须放在每轮处理完所有动作之后。如果你把它放在循环最前面意味着第0秒开始之前就叫“第1秒”整个时间轴会整体前移到头来所有答案都差1秒。4.2 更精确的改良版睡醒后先判断再跑前面提到过原题描述是“兔子休息结束后若发现仍然领先t米以上则继续休息”。但上述代码的做法是restTime减到0后兔子会先跑一秒跑完再判断是否要休息。严格来说这一秒里兔子可能“该睡却没睡”。如果对这个细节比较敏感可以这样改良当restTime减到0时先不急着跑而是判断当前距离差是否仍然 t如果是直接让兔子继续休息s秒。while (distR l distTurtle l) { distTurtle v2; if (restTime 0) { restTime--; } else { // 先判断睡醒后是否还处于领先状态 if (distR - distTurtle t) { restTime s; restTime--; // 当前这一秒已经算在休息里 } else { distR v1; if (distR - distTurtle t) { restTime s; } } } time; }仔细体会restTime--这行兔子在清醒状态下醒来发现还领先于是决定“这一秒开始继续睡”那么当前一秒已经消耗掉未来剩余休息时间为s秒再减掉这一秒所以是s - 1。如果担心自己搞混建议跑一遍这组样例把两个版本的输出对比一下深入理解“时间粒度”的差异。我个人在比赛时还是用前一个版本代码更短变量语义更简单出错概率低。多数评测数据不会专门构造“兔子醒来后仍需继续睡且影响最终结果”的极端用例两个版本都能过。但理解这个差异对以后处理更复杂的模拟题有好处。5. 边界条件最容易翻车的三个瞬间模拟题表面看是“照着题目写流程”但真正拉开差距的往往是边界处理。这道题我踩过的坑有三个分别说给你听。5.1 while(true)导致的死循环陷阱新手很流行写while(true)然后在循环里用break。一旦break条件写错比如只判断了兔子到终点而忘了乌龟赛事就会无限进行下去——兔子睡了醒、醒了睡乌龟一直在跑但你永远等不到循环退出评测系统直接判超时。我的建议是把结束条件直接写进while条件里也就是distR l distTurtle l。这样循环退出只可能是两件事兔子到终点或者乌龟到终点。如果两者同时在某秒越线那就在退出后统一判断逻辑上没有任何死角。5.2 同时到终点D一定要最先判断循环结束后会出现三种情况兔子先到、乌龟先到、同时到。判断顺序非常重要D必须放最前面。原因在于循环条件是“双方都未到终点才继续”一旦退出可能出现distR l且distTurtle l同时成立。如果先判断兔子获胜就会把平局误判成R答案错误。有读者可能会问能否把循环条件改成distR ! l distTurtle ! l千万别。因为兔子可能一步直接越过终点线距离不是正好等于l而是大于l。用不等号判断会让循环无法退出或者多跑很久。5.3 兔子“白跑”的那一秒误差从哪来我在4.2提到的改良版正是为了消除“兔子原本该继续睡却跑了一秒”的误差。再展开说一下这种误差只在极端情况下才影响最终答案。比如兔子领先差距已经很大醒来后显然要继续睡但简单版会让它跑一秒再睡这一秒虽然不会改变“兔子会继续休息”的事实却会影响兔子后续的位置进而可能影响比赛最终是兔子赢还是乌龟赢。如果你不想在这个细节上纠结就先用简单版。但如果本地自测时发现某个用例的输出和手动推演不一致别急着怀疑代码先用修行版跑一遍往往就能定位到“兔子多跑一秒”这个问题。6. 三组测试数据验证你的代码没写歪代码写完不测试等于白写。下面三组数据覆盖兔子赢、乌龟赢、平局三种结局也是我自己调代码时反复使用的自测用例。6.1 样例推演10 5 50 60 1000输入10 5 50 60 1000期望输出R 220简单推演关键节点兔子每秒比乌龟快5米第10秒时兔子跑100米乌龟跑50米距离差正好50触发休息60秒。休息结束时兔子仍停在100米乌龟已经跑了350米兔子反而落后。之后兔子以每秒10米追赶乌龟每秒5米继续爬。兔子从第70秒开始重新奔跑到第190秒距离差再次达到50米触发第二次休息。但第二次休息还没结束兔子就再也没追平乌龟了。因为从第70秒到第220秒兔子一直在追赶期间它在第220秒首先到达1000米终点线乌龟此时在1100米位置。结果是兔子胜用时220秒。6.2 平局样例1 1 10 20 100输入1 1 10 20 100兔子乌龟速度完全相同都是每秒1米无论跑多久距离差始终是0永远达不到t 10的休息阈值。双方匀速跑完100米同时到达输出D 100这组数据用来验证“速度相等时不会误触发休息”以及平局输出格式是否正确。6.3 兔子提前到终点5 2 30 20 100输入5 2 30 20 100兔子每秒5米乌龟每秒2米。第10秒时兔子50米、乌龟20米距离差30触发休息20秒。休息结束时乌龟到了60米兔子还在50米。之后兔子重新追赶第30秒开始跑第40秒时兔子正好到100米乌龟在80米兔子胜。输出R 40这组数据的价值在于兔子在第二次追赶过程中还没有再次触发休息条件就先到了终点用来验证“优先判断终点而非等待休息触发”的顺序没有问题。把这几个用例全部跑通后建议再自己构造一组数据l设置得很小比如50让兔子在第一秒就越过终点再构造一组乌龟先到终点的数据比如v1 2v2 5l 30。把这些极端情况都过一遍代码基本就稳了。我在实际调这道题时的体会是模拟题最怕的不是“想不到”而是“想当然”。龟兔赛跑预测里兔子的每一个动作背后都藏着条件把条件全部列出来再写代码远比闷头写几百行然后对着错误输出猜要快。后来再做类似的状态切换模拟题比如时钟问题、机器人巡线、电梯调度我都沿用这套方法论先定状态变量再列切换条件最后选一个合适的时间步长推进。希望这篇内容能帮你把这道基础练习彻底吃透下次碰到“带状态的模拟题”心里有底。