夜雨聆风学习资料网

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)

答案明天在评论区公布 · 代码成长记 · 算法篇连载中

每天一道练习题,陪孩子练出编程脑 · 代码成长记 · 算法篇开启

杰睿思少儿编程杰出少年 · 睿智创作 · 思维创新 · 探索科技 · 未来可期

关注杰睿思,陪孩子一起理解未来

相关学习资料