ARTICLE · 1184444
模拟题解法

模拟就是"题目怎么说,你就怎么写"——把规则一条条翻译成代码,跟着走
上节课学了枚举——把所有可能列出来一个个试。这节课,王老师在黑板上写了另一道题:
报数游戏
8 个小朋友围成一圈,从 1 号开始按顺序报数(1、2、3、1、2、3……)。 报到 3 的人退出圈子,然后从下一个人继续从 1 开始报。 最后剩下的那个人是几号?
小睿想了半天——"老师,这道题怎么枚举?好像没有什么'可能'可以试……"
"对,这道题不是枚举题。"王老师说,"枚举是'试所有可能',但这道题只有一个确定的过程——题目怎么说,你就怎么写,一步步跟着走。这叫模拟。"
模拟(Simulation):不找规律、不推导公式,把题目描述的规则一条条翻译成代码,让电脑按规则"演"一遍,看最后结果是什么。
这节课我们做三件事:用模拟解报数游戏,看状态变量怎么设计;总结模拟的核心——规则翻译成代码,边界处理好;最后对比模拟和枚举的区别。
1
模拟的思路:把规则翻译成代码
"模拟的第一步,不是写代码,是读懂规则。"王老师在黑板上拆解报数游戏的规则:
报数游戏的规则拆解
① 8 个人围成一圈,编号 1-8 ② 从 1 号开始,按 1、2、3、1、2、3…… 报数 ③ 报到 3 的人退出 ④ 从退出的人的下一个人继续从 1 报 ⑤ 重复 ②③④,直到只剩 1 个人 ⑥ 输出剩下那个人的编号
"读懂规则后,第二步——设计状态变量。"王老师说,"模拟就是让电脑'演'这个过程,所以需要变量记录'演到哪一步了'。"
小睿想了想——需要记录什么?
报数游戏需要的状态变量
a[8]:数组,记录每个人是否还在圈里(0=退出,非0=编号)pos:当前报到第几个人(位置)count:当前报的数(1、2、3……)alive:还剩多少人在圈里
"状态变量设计好了,模拟就成功了一大半。"王老师说,"剩下的就是把规则翻译成循环。"
小睿在笔记本上写:模拟三步:读规则 → 设计状态变量 → 把规则翻译成循环。
2
报数游戏的模拟代码
"我们来写代码。"王老师在黑板上写:
// 报数游戏 · 模拟版int n = 8, m = 3;int a[8];for (int i = 0; i < n; i++) a[i] = i + 1; // 编号1-8int pos = 0; // 当前位置int count = 0; // 当前报数int alive = n; // 剩余人数while (alive > 1) { // 直到剩1人 if (a[pos] != 0) { // 这人还在圈里 count++; // 报数+1 if (count == m) { // 报到m了 a[pos] = 0; // 退出 count = 0; // 重置报数 alive--; // 人数-1 } } pos = (pos + 1) % n; // 下一个人(循环)}// 输出剩下的人for (int i = 0; i < n; i++) { if (a[i] != 0) cout << a[i] << endl;}
"我们手动演一遍,验证代码对不对。"王老师说,"n=8,m=3,8个人围成一圈:"
手动模拟过程
初始:[1, 2, 3, 4, 5, 6, 7, 8],alive=8 第1轮:1→报1,2→报2,3→报3退出,4→报1,5→报2,6→报3退出,7→报1,8→报2 第2轮:1→报3退出,2→报1,4→报2,5→报3退出,7→报1,8→报2 第3轮:2→报3退出,4→报1,7→报2,8→报3退出第4轮:4→报1,7→报2,4→报3退出
剩下:7答案:7 号
"看——"王老师说,"代码就是规则的翻译。规则说'报到3退出',代码就写 if (count == m) a[pos] = 0;规则说'从下一个人继续',代码就写 pos = (pos + 1) % n。"
小睿点点头——模拟不需要想巧妙的算法,只要把规则一条条翻译成代码就行。
3
模拟的关键:边界处理
"模拟题看似简单,但最容易错的是边界。"王老师说,"我们看几个常见的坑:"
坑一:循环边界——报数到了队尾怎么办?
报数游戏里,pos 到了 7(最后一个人),下一个应该回到 0(第一个人)。 如果写 pos = pos + 1,pos 会变成 8,数组越界! 正确写法:pos = (pos + 1) % n——用取模实现"循环"。
坑二:跳过已退出的人
退出的人不能再报数,但 pos 还是会经过他。 所以报数前要先判断 a[pos] != 0,只有还在圈里的人才报数。 如果忘了这个判断,退出的人也会"报数",结果就错了。
坑三:退出后报数重置
报到 m 的人退出后,下一个人要从 1 开始报,所以 count 要重置为 0。 如果忘了重置,下一个人报的就是 4 而不是 1,全乱了。
"模拟题的难点不在算法,在细节。"王老师说,"一个边界没处理好,结果就错。所以写模拟题一定要:先手动模拟小例子验证,再跑大数据。"
模拟题做题流程
① 读题:把规则一条条列出来,不遗漏 ② 设计状态变量:需要记录什么?用什么数据结构? ③ 翻译规则:每条规则对应一段代码 ④ 手动模拟:用小数据(比如 n=8, m=3)手动走一遍,验证逻辑 ⑤ 检查边界:队首/队尾、空/满、第一次/最后一次 ⑥ 提交:确认无误后再跑大数据
在杰睿思的课堂上,王老师反复强调:模拟题是信奥赛的"送分题"——思路人人都懂,但分拿不拿得到,看你细不细心。
4
模拟 vs 枚举:两种"笨办法"
"老师,模拟和枚举都是'笨办法',它们有什么区别?"小睿问。
"好问题。"王老师在黑板上画了个对比:
枚举 vs 模拟
枚举:试所有可能的答案 · 有一个"答案空间",一个个试 · 例子:百钱买百鸡(试所有 x, y, z 组合) · 优化方向:消元、缩小范围模拟:按规则走一遍过程 · 没有"答案空间",只有一个确定的过程 · 例子:报数游戏(按规则报数,看最后剩谁) · 优化方向:通常不需要优化,重点是边界正确
"枚举是'猜答案',模拟是'走过程'。"王老师总结,"枚举题你能看到'所有可能',模拟题你只能看到'规则'。但它们有个共同点——不需要想巧妙算法,照着做就行。所以信奥赛里,枚举和模拟是最基础、也最常考的两类题。"
小睿突然觉得算法世界清晰了——递归是"拆",二分是"砍",枚举是"试",模拟是"走"。四种思路,覆盖了大部分基础题。
下课前,王老师留了个引子:"下节课我们做一道经典模拟题——日期计算。给你一个日期,算出它是星期几。闰年、大小月,全是边界——正好练模拟的细心。"小睿有点期待——日期计算听起来不难,但闰年规则他记得不太清楚……
放学后,小睿在笔记本上写了几条:
① 模拟 = 题目怎么说就怎么写,按规则一步步走
② 模拟三步:读规则 → 设计状态变量 → 翻译成循环
③ 报数游戏:数组+pos+count+alive,循环报数到3退出
④ 边界坑:循环取模、跳过退出的人、退出后重置报数
⑤ 模拟题是送分题——思路人人懂,分拿不拿得到看细心
⑥ 枚举是"猜答案",模拟是"走过程"——都是基础常考题
⑦ 四种思路:递归"拆"、二分"砍"、枚举"试"、模拟"走"
—
枚举是试答案,模拟是走过程。模拟不用想巧算法,规则翻译成代码就行。报数游戏,数组记录谁在圈里。报到三退出,取模实现循环。模拟题是送分题,难点在边界。先手动模拟小例子,再跑大数据。细心,是模拟题的核心。
这是「杰睿思·未来可期」代码成长记系列 · 第四十九篇 · 算法篇第八讲。枚举与模拟组第二篇。承接第48篇"题目怎么说就怎么写"的引子,用报数游戏讲模拟:状态变量设计(数组+pos+count+alive)、规则翻译成代码、三个边界坑(取模循环、跳过退出、重置报数)、模拟题做题六步流程。点出枚举"猜答案"vs模拟"走过程"的区别,四种基础思路(拆/砍/试/走)覆盖大部分题。下节课讲日期计算。
✅ 昨日答案 · 第058期 |
枚举优化 "百钱买百鸡"问题中,小睿写了三重循环枚举公鸡 x、母鸡 y、小鸡 z。王老师说可以优化成两重循环。优化的关键依据是什么? // 三重循环(优化前)for (int x = 0; x <= 20; x++) for (int y = 0; y <= 33; y++) for (int z = 0; z <= 100; z++) if (x+y+z==100 && 5*x+3*y+z/3==100) ... A. z 的范围太大,应该缩小到 0 到 33 B. ✓ 由方程 x+y+z=100 可得 z=100-x-y,所以 z 不用枚举 C. x 的范围可以从 0-20 缩小到 0-14 D. 把 z/3 改成 (z+2)/3 避免整数除法误差 解析 答案是B。题目给了两个方程:x+y+z=100(只数)和 5x+3y+z/3=100(钱数)。三个未知数两个方程,所以可以用第一个方程把 z 表示出来:z=100-x-y。这样 z 就不用枚举了——只枚举 x 和 y,z 直接算,从三重循环降到两重循环,迭代次数从约7万次降到约700次。这就是第48篇讲的"消元"优化:能算出来的就不要枚举。选项C(缩小x范围)是进一步优化,选项D是修整数除法,都不是"从三重降到两重"的关键依据。 |
📝 每日一练 · 第059期 |
报数游戏 报数游戏中,n 个人围成一圈,从 1 号开始按 1、2、3、1、2、3…… 报数,报到 3 的人退出。在模拟代码中,pos = (pos + 1) % n 这行代码的作用是什么? while (alive > 1) { if (a[pos] != 0) { count++; if (count == m) { a[pos] = 0; count = 0; alive--; } }pos = (pos + 1) % n; // 这行代码的作用?} A. 让报数从 1 重新开始 B. 让位置 pos 到队尾后回到队首,实现"围成一圈" C. 让退出的人从数组中删除 D. 让剩余人数 alive 减 1 在下方留言区回复你的答案(A/B/C/D) 答案明天在评论区公布 · 代码成长记 · 算法篇连载中 |
每天一道练习题,陪孩子练出编程脑 · 代码成长记 · 算法篇开启
杰睿思少儿编程杰出少年 · 睿智创作 · 思维创新 · 探索科技 · 未来可期
关注杰睿思,陪孩子一起理解未来















































































