项目建议 90 分钟学习等级 5/8

PYTHON LESSON 080

综合项目:数论密室逃脱

把本单元的辗转相除、唯一分解、素数筛法、高精度加法与二分查找组合成一个完整的密室逃脱项目。

00 · 学习目标

这一课要解决什么?

先想一想把辗转相除、分解质因数、筛法、高精度和二分装进同一个密室游戏,会怎样?
完成任务数论密室逃脱游戏
学习顺序需求 → 模块拆分 → 分步实现 → 系统测试 → 讲解

学完后,你应该能够

  • 把算法封装成可复用的工具函数
  • 工具串联:一个函数的输出喂给下一个
  • 主流程:解谜 → 合成 → 开锁

01 · 核心概念

数论 + 筛法 + 高精度 + 二分的综合运用

数论 + 筛法 + 高精度 + 二分的综合运用:密室里藏着三把数字钥匙:古老卷轴上的最大公因数、360 扇门的约数个数、第 15 个质数。三把钥匙用高精度加法合成最终密码,再用二分查找试开密码锁——整个游戏把本单元五大武功串成一条故事线。

考级对应GESP Python 5 级 · 初等数论GESP Python 5 级 · 高精度运算GESP Python 5 级 · 二分查找
学习边界

大整数质性测试和密码学分解需要更高级算法,不应把试除法扩展到任意规模。

02 · 语法与规则

先记住这 3 条,再开始写程序

01把算法封装成可复用的工具函数

先准确读出这条写法的结构与作用。

02工具串联:一个函数的输出喂给下一个

换一组最小数据,手工推演一次结果。

03主流程:解谜 → 合成 → 开锁

再用边界值或反例确认它的适用条件。

03 · 完整实例

代码、运行结果和解释放在一起看

g5-number-escape.pyPYTHON 3.12
# 综合项目:数论密室逃脱——集齐三把钥匙,打开最终密码锁

# 工具一:辗转相除法
def gcd(a, b):
    while b != 0:
        a, b = b, a % b
    return a

# 工具二:质因数分解(唯一分解定理)
def factorize(n):
    parts = []
    i = 2
    m = n
    while i * i <= m:
        c = 0
        while m % i == 0:
            m = m // i
            c = c + 1
        if c > 0:
            parts.append((i, c))
        i = i + 1
    if m > 1:
        parts.append((m, 1))
    return parts

# 工具三:素数筛法
def sieve(n):
    is_p = [True] * (n + 1)
    is_p[0] = is_p[1] = False
    for i in range(2, n + 1):
        if is_p[i]:
            for j in range(i * i, n + 1, i):
                is_p[j] = False
    return [x for x in range(2, n + 1) if is_p[x]]

# 工具四:高精度加法(字符串大数)
def big_add(a, b):
    x = [int(c) for c in a[::-1]]
    y = [int(c) for c in b[::-1]]
    n = max(len(x), len(y))
    x = x + [0] * (n - len(x))
    y = y + [0] * (n - len(y))
    carry = 0
    res = []
    for i in range(n):
        s = x[i] + y[i] + carry
        res.append(s % 10)
        carry = s // 10
    if carry > 0:
        res.append(carry)
    return "".join(str(d) for d in res[::-1])

# 第一把钥匙:古老卷轴上的最大公因数
key1 = gcd(1071, 462)
print("第一把钥匙:gcd(1071, 462) =", key1)

# 第二把钥匙:360 扇门的约数个数
cnt = 1
for p, c in factorize(360):
    cnt = cnt * (c + 1)
print("第二把钥匙:360 的约数个数 =", cnt)

# 第三把钥匙:第 15 个质数
primes = sieve(60)
key3 = primes[14]
print("第三把钥匙:第 15 个质数 =", key3)

# 高精度合成最终密码
password = big_add(str(key1), str(cnt))
password = big_add(password, str(key3))
print("三把钥匙合成密码:", password)

# 密码锁 1~100:用二分查找最快试开
def unlock(target):
    left, right = 1, 100
    times = 0
    while left <= right:
        times = times + 1
        mid = (left + right) // 2
        if mid == target:
            return times
        elif mid < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1

print("二分试开", password, "号锁,只用了", unlock(int(password)), "次")
print("密室逃脱成功!")
运行结果OUTPUT
第一把钥匙:gcd(1071, 462) = 21
第二把钥匙:360 的约数个数 = 24
第三把钥匙:第 15 个质数 = 47
三把钥匙合成密码: 92
二分试开 92 号锁,只用了 6 次
密室逃脱成功!

三把钥匙 21、24、47 用高精度加法合成 92;二分在 1~100 里试开 92,轨迹 50→75→88→94→91→92,正好 6 次。

在新标签运行和修改这个实例已装入本课代码 · 可自定义输入 · 可提交判题

04 · 逐步理解

每一步只解决一个问题

  1. 01

    项目蓝图

    密室里藏着三把数字钥匙:古老卷轴上的最大公因数、360 扇门的约数个数、第 15 个质数。三把钥匙用高精度加法合成最终密码,再用二分查找试开密码锁——整个游戏把本单元五大武功串成一条故事线。

  2. 02

    打造工具箱

    把前面几课写的 gcd、factorize、sieve、big_add 原样搬来,改造成可复用的工具函数。写好一个项目的秘诀,就是每个函数只干一件事,并且干得漂亮。

    PYTHON
    def gcd(a, b):
        while b != 0:
            a, b = b, a % b
        return a
  3. 03

    第一把钥匙:辗转相除

    卷轴上写着 1071 和 462,gcd 算出 21。想一想为什么不用短除法——因为辗转相除只要几步就能跑完,再大的数也不在话下。

    PYTHON
    key1 = gcd(1071, 462)
  4. 04

    第二把钥匙:约数个数

    360 = 2³ × 3² × 5,factorize 给出(质因数, 次数)清单,指数各加 1 再相乘得 24。分解加计数公式,两步都是质因数分解机那一课的手艺。

    PYTHON
    for p, c in factorize(360):
        cnt = cnt * (c + 1)
  5. 05

    第三把钥匙:筛法点名

    sieve(60) 把 60 以内的质数一网打尽,第 15 个(下标 14)是 47。注意名单下标从 0 开始数,下标比序号小 1,可别数错了。

    PYTHON
    primes = sieve(60)
    key3 = primes[14]
  6. 06

    高精度合成与二分开锁

    三把钥匙用 big_add 合成 92。这次的数字不大,但 big_add 能扛住几百位的密码——换成天文数字也照开。最后二分试开 1~100 的锁,6 次命中。

    PYTHON
    password = big_add(str(key1), str(cnt))
    password = big_add(password, str(key3))
  7. 07

    设计你自己的密室

    换掉线索数字,或者再加一把“第 k 个质数”“约数和”钥匙,把游戏讲给同学玩。能把算法给别人讲明白,才是真正学会了。

05 · 练习与检验

自己写出来,才算真正学会

本课实作步骤
  1. 1运行游戏,看三把钥匙怎么到手
  2. 2读懂 factorize 并手算 360 的分解
  3. 3解释 big_add 为什么能算任意大的和
  4. 4改动线索数字,设计自己的密室
打开本课编程实验室编辑、运行、测试、判题都在一个页面完成

06 · 完整知识

继续理解定义、规则和适用边界

第一次学习先完成上面的六个步骤;需要查定义、核对规则、分析误区或理解“为什么”时,再展开对应知识章。

算法方法质数、约数、最大公因数与筛法建立整数整除体系,掌握试除、欧几里得算法、唯一分解和筛法的条件与复杂度。

正式定义

若整数 a 能被非零整数 b 整除,则 b 是 a 的约数。大于 1 且只有 1 和自身两个正约数的整数是质数;每个大于 1 的整数都能唯一分解为质数幂的乘积(忽略次序)。

必须掌握

  • 0 和 1 都不是质数;判定 n 是否为质数只需试除到 floor(sqrt(n))。
  • gcd(a,b)=gcd(b,a mod b) 构成欧几里得算法;lcm(a,b)=abs(a//gcd(a,b)*b) 并要处理 0。
  • 约数成对出现,可枚举到平方根;完全平方数的平方根只计一次。
  • 埃氏筛从 p² 开始标记质数 p 的倍数,总体 O(n log log n);线性筛保证每个合数被最小质因子筛一次。
  • 分解质因数后,约数个数与约数和可以由各质因数指数公式计算。
  • 模运算支持加减乘分配;模除法不能直接用整数除法,需满足可逆条件并求逆元。

常见误区

  • 把 1 判成质数
  • 试除上界漏掉平方根
  • 完全平方数的约数重复统计
  • 取模后直接做普通除法

适用边界

  • 大整数质性测试和密码学分解需要更高级算法,不应把试除法扩展到任意规模。
  • 题目若涉及负数约数、0 的约数或模数非质数,必须先明确数学定义。
打开本章完整示例与独立阅读页 →
算法方法算法、复杂度与解题验证把题意转成输入、状态、规则与输出,用正确性和复杂度共同评价解法。

正式定义

算法是解决一类问题的有限、明确步骤。正确性说明算法对所有满足前置条件的输入都得到规定结果;时间和空间复杂度描述输入规模增长时资源使用的增长量级。

必须掌握

  • 先明确输入规模 n、数据范围、目标和允许误差,再选择数据结构与算法。
  • O(1)、O(log n)、O(n)、O(n log n)、O(n²)、O(2ⁿ) 表示增长量级,不是精确运行秒数。
  • 顺序代码复杂度取较大项,嵌套循环常相乘,二分每步把范围缩小一半。
  • 正确性可用循环不变量、数学归纳、交换论证、反证或状态定义来说明。
  • 样例只验证少量输入;必须自己设计边界、极端、重复、有序/逆序和无解数据。
  • 优化前先得到正确基线并测量瓶颈,不为小数据盲目增加复杂实现。

常见误区

  • 只看样例通过就宣称正确
  • 不看数据范围使用 O(n²)
  • 二分区间开闭混用
  • 把 O(n) 当成永远比 O(log n) 慢固定倍数

适用边界

  • 复杂度隐藏常数与硬件差异,但仍是比较规模增长的核心工具。
  • 考场策略、课程完成度和算法能力是不同证据,任何单项都不能保证考级通过。
打开本章完整示例与独立阅读页 →
Python 基础运算符、表达式与优先级完整区分算术、比较、逻辑、成员、身份和位运算,并用优先级表消除歧义。

正式定义

表达式求值得到一个值。运算符规定如何组合操作数;当一个表达式含多个运算符时,优先级和结合方向决定求值顺序,括号可以明确改变顺序。

必须掌握

  • / 总是得到浮点结果;// 是向负无穷方向取整的整除;% 与 // 满足 a == (a // b) * b + a % b。
  • 比较可以链式书写,如 0 <= x < 10;and/or 会短路并返回最后求值的操作数,不一定返回 bool。
  • == 比较值是否相等,is 比较是否为同一个对象;判断 None 应写 is None。
  • in/not in 做成员测试;对 dict 测试的是键。
  • 位运算作用于整数的二进制位;负整数按无限长二进制补码语义理解。
  • 复杂表达式即使能靠优先级正确运行,也应使用括号表达意图。

常见误区

  • 把 // 当成简单截断
  • 用 is 比较数字或字符串的值
  • 忘记 and 的优先级高于 or
  • 连续位移、比较和逻辑运算却不加括号

适用边界

  • 浮点数比较受二进制表示误差影响,需要按问题选择容差。
  • 运算符可由自定义类重载,因此相同符号对不同类型可能有不同语义。
打开本章完整示例与独立阅读页 →
程序组织模块、导入与常用标准库正确导入模块,并按用途查找 math、random、statistics、collections、heapq、bisect 等标准工具。

正式定义

模块是可导入的 Python 代码单元,包把模块组织成层次。import 先加载模块并创建模块对象,再把名字绑定到当前命名空间;标准库随 Python 分发,不等于第三方包。

必须掌握

  • import module 保留清晰命名空间;from module import name 只导入指定名字;避免 from module import *。
  • math 提供 sqrt、floor、ceil、gcd、log、sin 等数学函数和 pi、e 等常量。
  • random 用于伪随机模拟,不适合密码安全;需要安全随机时使用 secrets。
  • collections.deque 适合双端队列,Counter 适合计数,defaultdict 可按工厂创建缺省值。
  • heapq 实现最小堆,bisect 在已排序序列中二分定位,itertools 提供高效迭代组合工具。
  • if __name__ == '__main__': 用来区分文件被直接运行还是被导入。

常见误区

  • 文件名写成 math.py 导致遮蔽标准库
  • import * 污染命名空间
  • 把 random 当成加密随机数
  • 没有理解工具的数据复杂度就盲目调用

适用边界

  • 标准库非常大,本章覆盖课程会实际依赖的入口;具体函数签名应结合本站条目与运行时帮助查询。
  • Turtle 需要图形窗口,本站文本判题环境不伪造其画布。
打开本章完整示例与独立阅读页 →

完成检查

确认自己会解释、会编写、会验证