CC++ & Algorithm

Python模拟算法

困难2
语言版本:C++Python
概述:模拟就是按照题目描述的规则,一步一步地让程序像演剧本一样执行。

模拟算法:让程序像演电影一样一步步执行

你有没有玩过“过家家”游戏?按照剧本,你扮演爸爸,我扮演妈妈,按顺序做一系列动作。编程中的模拟算法就像这样:我们先把“剧本”(题目的要求)读透,然后让计算机一步步照做,最后得到结果。在信息学竞赛和日常小项目中,很多问题都适合用模拟来解决,比如机器人走迷宫、排队买奶茶、时钟走动、游戏中的物理碰撞等等。

什么是模拟算法?

模拟算法的核心是忠实还原过程。程序里通常有几个状态变量记录当前情况,然后写一个循环,每次循环里按规则更新这些变量,直到满足结束条件。它不需要高深的数学公式,只需要细心和耐心。

生活中的模拟

  • 模拟课间操:先踏步30秒,然后做伸展运动30秒,再跳跃运动30秒,循环3遍。我们可以记录当前是第几遍、正在做什么动作、剩余时间。
  • 模拟自动售货机:投入硬币→选择商品→检查余额→输出商品→找零。每一步都有明确规则。
  • 模拟投篮比赛:小明和小红每人投10次,每次进球得2分,不进球不得分。我们可以用随机数模拟每次投篮结果,然后统计总分。

分要点讲解模拟算法的关键技巧

1. 用变量记录当前状态

状态变量就像剧本里的“当前场景”。需要提前想清楚哪些信息需要记住。

例如模拟一个倒计时:只需要记录剩余秒数。

timer = 60          # 初始倒计时60秒
while timer > 0:
    print(f"剩余{timer}秒")
    timer -= 1      # 每秒减1
print("时间到!")

再比如模拟零食售卖机:需要记录当前投入的总金额、库存中每种零食的剩余数量、是否已选择商品等。

2. 按照规则更新状态

规则通常写在题目的文字里。我们要逐字翻译成代码。常见的更新包括:

  • 加法/乘法(例如走路距离累加)
  • 条件判断(例如如果余额足够就扣钱并出货)
  • 循环嵌套(例如模拟多轮比赛)

下面是一个模拟考试计分的例子:一场考试有3道题,每题10分,答对得10分,打错不得分。我们用随机数模拟小明答题的正确率(60%概率答对),最后输出总分。

import random

total_score = 0                 # 总成绩
for question in range(1, 4):    # 3道题
    is_correct = random.random() < 0.6  # 60%概率正确
    if is_correct:
        score = 10
        print(f"第{question}题答对,+10分")
    else:
        score = 0
        print(f"第{question}题答错,+0分")
    total_score += score

print(f"最终得分:{total_score}分")

3. 用循环控制模拟过程

模拟一般需要重复执行,所以常用 whilefor 循环。循环的条件就是“模拟还没结束”。

  • for 循环适合已知步数(例如走100步、模拟100次投掷)
  • while 循环适合直到某个条件才停(例如直到有人达到终点、直到余额不足)

4. 处理边界条件

很多模拟题的难点在于“边界”。比如时间进位、数组下标越界、分数正好达到满分时的处理。边界条件往往藏在题目的细节里。

比如模拟一个24小时制的电子钟:从 00:00 开始,每秒钟加1,到 23:59:59 之后跳回 00:00:00。我们可以用三个变量,分钟和秒到60时要归零并进位,小时到24时要归零。

hour = 0
minute = 0
second = 0
for _ in range(86400):  # 模拟一整天,86400秒
    print(f"{hour:02d}:{minute:02d}:{second:02d}")
    second += 1
    if second == 60:
        second = 0
        minute += 1
        if minute == 60:
            minute = 0
            hour += 1
            if hour == 24:
                hour = 0

5. 多用 print 调试

模拟程序如果结果不对,最有效的办法就是在关键位置打印当前状态,看看是否和预期一致。这就像拍电影时导演喊“卡”,检查一下演员站位对不对。

新手容易犯的错误

  • 忘记更新状态变量:比如在循环里用了 rabbit_pos += 10,但忘记写 step += 1,导致死循环。
  • 死循环:循环条件永远为真,例如 while rabbit_pos < 100 但兔子位置一直不增加。
  • 边界条件考虑不周:比如在钟表模拟中 if minute == 60 应该是 minute == 60,但如果写成 minute > 60 就可能出错;或者小时进位后忘记重置 minute = 0
  • 数组下标越界:用列表模拟排队时,索引从0开始,如果访问 queue[5] 但列表只有5个元素(索引0~4)就会报错。
  • 精度问题:在模拟分数或小数时,浮点数比较要小心,最好用整数(比如把所有量都乘以10或100变成整数)。

完整示例:模拟“石头剪刀布”五局三胜

我们来写一个完整的模拟程序:小明和小红玩石头剪刀布,每局赢的人得1分,先得3分为胜者。程序用随机数生成两人的出拳,并输出每局的结果和最终胜者。

import random

# 出拳选项
options = ["石头", "剪刀", "布"]
# 初始得分
score_m = 0     # 小明得分
score_h = 0     # 小红得分
round = 0       # 当前局数

print("石头剪刀布五局三胜开始!")
# 只要两人都还没达到3分就继续
while score_m < 3 and score_h < 3:
    round += 1
    # 随机出拳
    choice_m = random.choice(options)    # 小明出拳
    choice_h = random.choice(options)    # 小红出拳
    print(f"第{round}局:小明出{choice_m},小红出{choice_h}")

    # 判断胜负
    if choice_m == choice_h:
        print("  平局!")
    elif (choice_m == "石头" and choice_h == "剪刀") or \
         (choice_m == "剪刀" and choice_h == "布") or \
         (choice_m == "布" and choice_h == "石头"):  # 小明赢
        score_m += 1
        print("  小明赢一局!")
    else:  # 小红赢
        score_h += 1
        print("  小红赢一局!")
    
    print(f"  当前比分:小明{score_m} : {score_h} 小红")

# 打印最终结果
print("\n比赛结束!")
if score_m == 3:
    print("小明以3:{}获胜!".format(score_h))
else:
    print("小红以3:{}获胜!".format(score_m))

运行这个程序,你会看到电脑随机模拟一局比赛,直到有人先得3分。你可以修改 random.choice 为固定值来测试边界情况(比如总是平局)。

模拟队列的深入例子:奶茶店排队

之前我们有一个“排队买票”的简单模拟,现在把它变得更真实:奶茶店有1个店员,每位顾客需要不等的时间(比如随机2~5秒),店员每次只能服务一个人,顾客按先来后到排队。模拟直到所有顾客离开。

import random

queue = ["小明", "小红", "小刚", "小丽", "小华"]   # 排队列表
time = 0                # 当前时刻(秒)

print("奶茶店开始营业,排队人数:", len(queue))
while len(queue) > 0:
    customer = queue.pop(0)          # 第一位顾客出队
    service_time = random.randint(2, 5)  # 随机服务时间2~5秒
    print(f"时间{time}秒:{customer}开始点单,需要{service_time}秒")
    time += service_time
    print(f"时间{time}秒:{customer}拿好奶茶离开")
    if queue:
        print(f"  剩余排队:{queue}")

print(f"所有顾客已服务完毕,总用时{time}秒")

这个例子展示了如何用列表模拟队列,以及如何用随机数让模拟更真实。

相关知识点指引

  • 枚举算法:当你需要列举所有可能情况时,枚举和模拟经常配合使用。
  • 循环结构forwhile 是模拟的骨架,需要熟练掌握。
  • 列表与字典:存储状态时,列表用于有序序列,字典用于键值对(例如顾客编号→点单内容)。
  • 随机数模块 random:让模拟包含不确定性,像真实世界一样。
  • 时间模块 time:可以用来控制模拟速度,让程序慢慢执行,方便观察。

模拟算法就像搭积木——你搭好每一步的规则,计算机就会忠实地执行。遇到复杂问题时,先在纸上演练一遍,再动手写代码,十有八九一次就能成功。记住:模拟题不怕难题,就怕粗心漏掉一个条件。祝你写代码顺利!

例题精讲

1单选题

以下代码模拟抛一枚均匀硬币1000次,统计正面(Heads)出现的次数。请选择正确的实现。

Aimport random; heads = sum(random.randint(0,1) == 1 for _ in range(1000))
Bimport random; heads = sum(random.randrange(2) for _ in range(1000))
Cimport random; heads = sum(random.choice(['H','T']) == 'H' for _ in range(1000))
Dimport random; heads = sum(random.uniform(0,1) > 0.5 for _ in range(1000))
2单选题

在模拟随机游走(从0开始,每次以等概率向左或向右移动1单位)100步后,最终位置的数学期望是多少?

A0
B50
C100
D不确定
3判断题

模拟算法(如蒙特卡洛方法)必须使用随机数生成器。

4填空题
以下代码模拟掷一个均匀六面骰子100次,统计每个面出现的次数。请补全代码。
import random
counts = [0]*6
for _ in range(100):
    face = random.randint(1,6)
    ___
5填空题
以下代码使用蒙特卡洛方法估算圆周率π。在单位正方形内随机投点,统计落在单位圆内的比例。请补全判断点是否在单位圆内的条件。
import random
inside = 0
N = 10000
for _ in range(N):
    x = random.random()
    y = random.random()
    if ___:
        inside += 1
pi = 4 * inside / N