好,那我们走 Stage 9——换换口味,从”纯数学证明”转到”算法与计算”。这一块气质和前八个 Stage 很不一样:它是数论和计算机科学的交叉,是你密码学腹地的核心工具库,而且对你这个 USACO Platinum 选手来说,会有种”回到主场”的熟悉感。

Confidence: 0.9


Stage 9:计算数论与素性/分解算法

前八个 Stage 你在”用纸笔证明”,这一阶段你在”设计能跑的算法”。核心问题变成:面对一个几百位的大数,怎么高效地判断它是不是素数、怎么分解它、怎么求模平方根、怎么破解离散对数? 这些是 RSA、椭圆曲线密码学的底层引擎。

五块:① Miller-Rabin 素性测试 → ② Pollard’s rho 分解 → ③ Tonelli-Shanks 模平方根 → ④ Pohlig-Hellman 离散对数 → ⑤ Hensel 提升。每块:解决什么 → 核心思想 → 完整步骤/例子 → 代码 → 陷阱


9.1 Miller-Rabin 素性测试 ★最重要

解决什么

快速判断一个大数(几百位)是不是素数。这是 RSA 生成密钥的第一步——你需要随机生成大素数,就得能快速验证”这个随机数是不是素数”。

为什么不能用试除法

判断 是否素数,最朴素的方法是试除到 。但如果 有 300 位, 有 150 位,试除要 次——宇宙毁灭都算不完。需要根本不同的方法。

核心思想:费马小定理的加强版

第一层(费马测试):费马小定理说,若 素数,则 。所以如果找到某个 使 , 铁定是合数

问题:有些合数也”骗过”费马测试(叫卡迈克尔数,如 561),对所有 都满足 。所以费马测试会误判。

第二层(二次探测):Miller-Rabin 加了一道关键检查。把 写成 ( 奇),然后利用一个事实:

是素数,则方程 只有两个解:

(因为 ,素数模下必有 。)

所以在计算 的过程中,如果发现一个”非平凡的 1 的平方根”(既不是 也不是 ,但平方后是 1),那 铁定是合数——因为素数不允许这种平方根存在。

完整算法步骤

判断 是否素数:

  1. ( 为奇数)
  2. 随机选一个底数 ()
  3. 计算
  4. ,通过这一轮(可能是素数),换下一个
  5. 否则,反复平方 次:
    • 每次
    • 若某次 ,通过这一轮
    • 若平方后变成 1 却没经过 抓到非平凡平方根, 是合数!
  6. 次平方都没出现 是合数
  7. 多换几个 重复(测 个底数),全通过则极大概率是素数

完整例子:测试 (卡迈克尔数,费马测试会骗过)

,,所以

:

  • 。算出
  • ,进入平方循环:
    • (不是 560)
    • (不是 560)
    • 平方变成了 1,但前一步是 67 不是 560!
  • 抓到非平凡平方根(),561 是合数!

(费马测试在这里会被骗:,但 Miller-Rabin 通过二次探测抓住了它。)

代码

import random
 
def miller_rabin(n, k=20):
    if n < 2: return False
    # 小素数快速排除
    for p in [2,3,5,7,11,13,17,19,23,29,31,37]:
        if n % p == 0:
            return n == p
    # 写 n-1 = 2^s * d
    d, s = n - 1, 0
    while d % 2 == 0:
        d //= 2
        s += 1
    # 测 k 个随机底数
    for _ in range(k):
        a = random.randrange(2, n - 1)
        x = pow(a, d, n)
        if x == 1 or x == n - 1:
            continue
        for _ in range(s - 1):
            x = x * x % n
            if x == n - 1:
                break
        else:
            return False   # 没找到 n-1,且抓到非平凡平方根 → 合数
    return True            # 所有底数都通过 → 极大概率素数
 
print(miller_rabin(561))            # False (卡迈克尔数被抓)
print(miller_rabin(10**18 + 9))     # True (大素数瞬间判定)
print(miller_rabin(2**61 - 1))      # True (梅森素数)

准确性

Miller-Rabin 是概率算法:测 个底数,每个合数被误判为素数的概率 。测 20 个底数,误判概率 ,实用上足够。

确定性版本:对 ,只需测固定的几个底数(2,3,5,7,11,13,17,19,23,29,31,37)就能确定判素,无需随机。

陷阱:⚠️ 关键是步骤 5 那个 for-else——只有”循环正常结束(没 break)“才返回合数。这个 Python 的 for-else 语法精确对应”没找到 “的逻辑,容易写错。


9.2 Pollard’s rho 整数分解

解决什么

Miller-Rabin 判断”是不是素数”,但如果是合数,怎么把它分解?Pollard’s rho 负责这个——尤其擅长找出大数的中等大小因子。

核心思想:生日悖论 + 伪随机游走

生日悖论:23 个人里就有较大概率两人同生日——因为”碰撞”比直觉快得多( 量级)。

应用到分解:设 有一个未知质因子 。我们造一串伪随机数 (用 )。这串数 会比 更早出现重复(因为 ,模 的”抽屉”更少,约 步就碰撞)。

关键:一旦 ,则 ,所以: 捞出了因子 !

名字来历

那串数最终会进入循环(因为模 只有有限个值),画出来像希腊字母 ρ (rho) 的形状——一条尾巴接一个环。

Floyd 判圈(龟兔赛跑)

为了检测碰撞又不存储所有值,用 Floyd 判圈:一只”乌龟”走一步、一只”兔子”走两步,它们的差 一旦 就找到因子。

完整例子:分解

,从 开始:

1526
2267474
37474871

找到因子 97!。✓

代码

from math import gcd
 
def pollard_rho(n):
    if n % 2 == 0: return 2
    x = y = 2
    c = 1
    d = 1
    f = lambda v: (v*v + c) % n
    while d == 1:
        x = f(x)           # 乌龟走一步
        y = f(f(y))        # 兔子走两步
        d = gcd(abs(x - y), n)
    return d if d != n else None   # 返回找到的因子
 
print(pollard_rho(8051))      # 97 (或 83)
print(pollard_rho(10403))     # 101 (10403=101×103)

陷阱:⚠️ Pollard’s rho 找到的是某个因子(不一定是素因子),需要递归分解;且偶尔失败(返回 本身),要换参数 重试。它对有小因子的数很快,但对两个大素数的积(如 RSA 模数)依然慢——这正是 RSA 安全的基础。


9.3 Tonelli-Shanks 模平方根

解决什么

的解(模平方根)。你 Stage 4 学了 的捷径(),但 时捷径失效,需要 Tonelli-Shanks。

核心思想(通俗版)

( 奇)。算法分两步:

  1. 先得到一个”接近”的候选
  2. 反复修正:这个候选的平方和 差一个”2 的幂次方根因子”,用一个二次非剩余构造的”修正因子”逐步调整,像调音一样把候选逼近真正的平方根

直觉:在循环群里,误差总是 阶的元素,每次修正消掉一层 2 的幂,有限步内归零。

完整例子:求

(捷径失效!),,

(这里 比较特殊,我给你看小例子的结果:)

  • 找一个非剩余,比如 3()
  • 经过 Tonelli-Shanks 的迭代修正……
  • 得到

验证: ✓。另一根

代码

def tonelli_shanks(a, p):
    # 求 x^2 ≡ a (mod p), 假设解存在
    if pow(a, (p-1)//2, p) != 1:
        return None   # a 不是二次剩余,无解
    if p % 4 == 3:
        return pow(a, (p+1)//4, p)   # 捷径
    # 一般情况
    q, s = p - 1, 0
    while q % 2 == 0:
        q //= 2; s += 1
    # 找一个二次非剩余 z
    z = 2
    while pow(z, (p-1)//2, p) != p - 1:
        z += 1
    m, c, t, r = s, pow(z, q, p), pow(a, q, p), pow(a, (q+1)//2, p)
    while t != 1:
        # 找最小 i 使 t^(2^i)=1
        i, temp = 0, t
        while temp != 1:
            temp = temp*temp % p; i += 1
        b = pow(c, 1 << (m-i-1), p)
        m, c, t, r = i, b*b % p, t*b*b % p, r*b % p
    return r
 
print(tonelli_shanks(2, 17))   # 6 (或 11)
x = tonelli_shanks(2, 17)
print(f"验证: {x}^2 mod 17 = {x*x % 17}")   # 2

用途:椭圆曲线上”给定 x 求 y”(因为 ,要开平方),所以是 ECC 的核心子程序。

陷阱:⚠️ 用前要先确认 是二次剩余(用欧拉判别 ),否则无解。


9.4 Pohlig-Hellman 离散对数

解决什么

求离散对数 。你 Stage 4 学了 BSGS(),但当群的阶 能分解成小质因子时,Pohlig-Hellman 快得多。

核心思想:CRT 用在离散对数上

这是 CRT 思想的绝妙应用。如果群的阶 :

  1. 把”求模 的离散对数”这个大问题,拆成在每个 子群里求小的离散对数
  2. 每个小问题规模只有 ,用 BSGS 快速解
  3. CRT 把各个 拼回完整的

直觉:大的离散对数难,但如果能”分而治之”成几个小的离散对数,再用 CRT 合并,总难度由最大的质因子决定,而不是 本身。

对密码学的致命启示 ★

这个算法揭示了一个密码学的核心安全原则:

如果群的阶有很多小质因子,离散对数就不安全!

所以 Diffie-Hellman、ECC 选参数时,必须让群的阶含一个大质因子(否则 Pohlig-Hellman 直接攻破)。这就是为什么密码学里要用”安全素数”( 也是素数)——保证群的阶 有大质因子

完整例子(骨架)

在阶 的群里:

  • 对每个 :把方程”投影”到 阶子群(两边取 次幂),解出
  • CRT 合并所有

代码(简化版)

from sympy.ntheory import discrete_log
from sympy import factorint
 
# sympy 的 discrete_log 内部就用了 Pohlig-Hellman + BSGS
# 求 g^x ≡ h (mod p)
p, g, h = 1019, 2, 5
x = discrete_log(p, h, g)
print(f"离散对数 x = {x}")
print(f"验证: {g}^{x} mod {p} = {pow(g, x, p)}")   # 应等于 h=5
 
# 展示群阶的因子分解(决定 Pohlig-Hellman 的效率)
print(f"群阶 p-1 = {p-1} = {factorint(p-1)}")

陷阱:⚠️ Pohlig-Hellman 只在”群阶光滑(smooth,即只有小质因子)“时快;若群阶有大质因子,它退化成 BSGS 的速度。密码学正是靠”故意让群阶有大质因子”来防御它。


9.5 Hensel 提升 (Hensel’s Lemma)

解决什么

解高次同余 。你解过线性的 ,但 这种”高次 + 质数幂模”需要 Hensel。

核心思想:牛顿迭代的数论版

“提升”:先在简单的模 下找到解,然后像牛顿迭代一样,把这个解一步步”提升”到模 ……每次提升用导数信息修正。

公式:若 ,则模 的解是: (和牛顿法 一模一样,只是在模 意义下)

完整例子:解

第一步(模 7):,试得 ()或 。取

第二步(提升到模 49):

  • ,,
  • ?这里要模 49 算:(因 )

验证:

所以 (另一根 )。

代码

def hensel_lift(f, df, p, k, r):
    # 把模 p 的解 r 提升到模 p^k
    mod = p
    for _ in range(k - 1):
        mod *= p
        fr = f(r) % mod
        dfr_inv = pow(df(r), -1, mod)   # f'(r) 的逆
        r = (r - fr * dfr_inv) % mod
    return r
 
f  = lambda x: x*x - 2
df = lambda x: 2*x
# 从模 7 的解 3 提升到模 7^2
root = hensel_lift(f, df, 7, 2, 3)
print(f"x^2≡2 mod 49 的解: {root}")           # 10
print(f"验证: {root}^2 mod 49 = {root*root % 49}")   # 2

用途:p-adic 数的基础、解高次同余、某些密码学构造。

陷阱:⚠️ Hensel 提升要求 (导数非零),否则提升可能失败或有多个/无解——这对应牛顿法在导数为零处失效。


Stage 9 全景速查表

算法解决什么核心思想复杂度
9.1 Miller-Rabin判素数费马 + 二次探测
9.2 Pollard’s rho分解生日悖论 + 判圈
9.3 Tonelli-Shanks模平方根逐层消 2 的幂
9.4 Pohlig-Hellman离散对数CRT 分而治之由最大质因子定
9.5 Hensel高次同余牛顿迭代提升 次提升

我的观点:Stage 9 对你而言是一个特别”对味”的阶段,我想说清它为什么和你高度契合,以及它在你大局中的关键位置。

第一,这一阶段是你的”主场”。 前八个 Stage 是纯数学证明,而 Stage 9 是算法——对你这个 USACO Platinum 选手来说,“设计高效算法”正是你的看家本领。你会发现这里的思维方式很熟悉:Pollard’s rho 用生日悖论(概率 + 复杂度分析)、Floyd 判圈(你 USACO 一定写过快慢指针)、Miller-Rabin 的位运算优化——这些都是竞赛编程的核心套路。前八个 Stage 你是在”学数学家怎么想”,这一阶段你是在用”你本来就擅长的算法思维”解决数论问题。 这种”回到主场”的感觉,会让你学得格外顺。

第二,也是最重要的——这一阶段是你密码学梦想的”工具锻造车间”。 我之前说 Stage 9 严格属于”计算数论”而非初等数论,所以从”主线”角度跳过了它。但从你的密码学兴趣角度,Stage 9 才是真正的核心装备库:Miller-Rabin 是 RSA 生成大素数的引擎、Pollard’s rho 是分解攻击的武器、Tonelli-Shanks 是椭圆曲线”求点”的必备子程序、Pohlig-Hellman 直接揭示了”为什么密码学参数要那样选”。你研究的 ECDLP、你向往的椭圆曲线密码,底层全是这些算法在支撑。 所以你选择走 Stage 9,其实是走向了比 Stage 10(二次互反)更贴近你真实目标的方向——这个选择很对。

第三,Stage 9 让你看到数论的”另一张面孔”。 你前八个 Stage 看到的数论是”优美的、证明的、纸笔的”;Stage 9 让你看到它”实用的、计算的、能跑的”一面。同一个费马小定理——在 Stage 3 它是个要证明的定理,在 Stage 9 (Miller-Rabin) 它变成了判素数的算法引擎;同一个 CRT——在 Stage 3 它是解同余方程组,在 Stage 9 (Pohlig-Hellman) 它变成了攻破离散对数的策略。这种”同一个数学,既能证明又能计算”的双重性,正是计算数论的魅力,也是连接”纯数学”和”密码学应用”的桥梁。你站在这座桥上,一边是你走过的八个 Stage,一边是你向往的椭圆曲线密码。

我特别想让你记住 9.1 和 9.4 这两个:Miller-Rabin 是”最该掌握的算法”(密码学、CP 都用),而 Pohlig-Hellman 揭示的”群阶要有大质因子”原则,是理解一切离散对数密码学安全性的钥匙——你将来学椭圆曲线密码,会反复回到这个原则。

关键提醒 (Caveats):

  • 我为每个算法给了可运行的代码,但有些是教学简化版(尤其 Tonelli-Shanks、Pohlig-Hellman 的完整工程实现更复杂),理解核心思想比记代码重要
  • Miller-Rabin 是概率算法(测 底数误判率 );但对小范围有确定性版本(固定底数),两者别混淆
  • Pollard’s rho 找到的是某个因子(可能是合数),需递归分解到素因子;且偶尔失败需换参数
  • 9.4 的代码我用了 sympy 的 discrete_log(内部含 Pohlig-Hellman),没手写完整算法——完整实现较长,理解”CRT 分而治之”的思想是关键
  • Tonelli-Shanks 我给的 例子,中间迭代细节略过了(直接给结果),完整迭代可以对照代码理解
  • 这些算法的”安全启示”(如 Pohlig-Hellman → 群阶要有大质因子)对你理解密码学至关重要,比算法本身更值得记
  • 本块代码用到 sympy,安装:pip install sympy -i https://pypi.tuna.tsinghua.edu.cn/simple

Stage 9 讲完了——你看到了数论的”算法面孔”:Miller-Rabin 判素数、Pollard’s rho 分解、Tonelli-Shanks 求模平方根、Pohlig-Hellman 攻离散对数、Hensel 提升解高次同余。这是你密码学腹地的核心工具库,也是你算法思维的主场。

下一步你挑:

  1. 出一套 Stage 9 练习题(算法实现 + 手算追踪,会偏编程一些,延续 PDF 格式)
  2. 进 Stage 10(二次互反完整理论,初等数论主线的最后收尾)
  3. 正式开启椭圆曲线/密码学新旅程(你的应许之地——Stage 9 刚锻造的这些工具,正是为它准备的;Miller-Rabin、Tonelli-Shanks 会在那里直接派上用场)