Modular Arithmetic 模运算

Modular Arithmetic 模运算

简介

在模运算中,我们不是直接处理整数本身,而是处理它们除以 mm 后的余数。我们称这个过程为取模 mm。例如,如果我们取 m=23m = 23,那么我们不是处理 x=247x = 247,而是使用 xmod23=17x \bmod 23 = 17

通常,mm 会是一个大素数,题目中会给出;最常见的两个值是 109+710^9 + 7998244353=119223+1998244353 = 119 \cdot 2^{23} + 1

模运算用于避免处理会溢出内置数据类型的数字,因为我们可以根据以下公式取余数:

  • (a+b)modm=((amodm)+(bmodm))modm(a + b) \bmod m = ((a \bmod m) + (b \bmod m)) \bmod m
  • (ab)modm=((amodm)(bmodm))modm(a - b) \bmod m = ((a \bmod m) - (b \bmod m)) \bmod m
  • (ab)modm=((amodm)(bmodm))modm(a \cdot b) \bmod m = ((a \bmod m) \cdot (b \bmod m)) \bmod m
  • abmodm=(amodm)bmodma^b \bmod m = (a \bmod m)^b \bmod m

模幂运算

为什么需要快速幂?

计算 xnmodmx^n \bmod m 时,如果直接使用循环乘 nn 次,时间复杂度是 O(n)O(n)。但当 nn 达到 10910^9 甚至更大时,这种方法就太慢了。

二进制幂次方

二进制幂次方(Binary Exponentiation)可以将时间复杂度降到 O(logn)O(\log n)

核心思想:将指数 nn 分解为二进制表示。例如: 510=510102=58525^{10} = 5^{1010_2} = 5^8 \cdot 5^2

如果我们知道 x1,x2,x4,,x2log2nx^1, x^2, x^4, \dots, x^{2^{\lfloor \log_2 n \rfloor}},就可以用 O(logn)O(\log n) 的时间计算出 xnx^n

实现

MOD = 10**9 + 7

def pow_mod(a, b, mod):
    """计算 a^b mod mod"""
    res = 1
    a %= mod
    while b:
        if b & 1:
            res = res * a % mod
        a = a * a % mod
        b >>= 1
    return res

Python 内置的 pow 函数已经实现了快速幂,可以直接使用:

MOD = 10**9 + 7

for _ in range(int(input())):
    first, second = map(int, input().split())
    print(pow(first, second, MOD))

模逆元

什么是模逆元?

模逆元相当于实数中的倒数。要将 aa 除以 bb,可以乘以 bb 的模逆元。

对于素数模 ppaa 的模逆元定义为满足以下条件的数 a1a^{-1}aa11(modp)a \cdot a^{-1} \equiv 1 \pmod{p}

例如,22p=109+7p = 10^9 + 7 的逆元是 i=p+12=5108+4=500000004i = \frac{p+1}{2} = 5 \cdot 10^8 + 4 = 500000004

验证: 2×50000000410000000081(mod1000000007)2 \times 500000004 \equiv 1000000008 \equiv 1 \pmod{1000000007}

费马小定理

费马小定理(不要与费马大定理混淆)指出,所有不被 pp 整除的整数 aa 都满足: ap11(modp)a^{p-1} \equiv 1 \pmod{p}

因此: ap2a1(modp)a^{p-2} \cdot a \equiv 1 \pmod{p}

所以 ap2a^{p-2} 就是 aapp 的逆元。

实现

MOD = 10**9 + 7

# 方法1:使用费马小定理
x = pow(2, MOD - 2, MOD)
print(x)  # 500000004
assert 2 * x % MOD == 1

扩展欧几里得算法

我们也可以通过扩展欧几里得算法求模逆元:

def modinv(x, mod):
    """扩展欧几里得算法求模逆元"""
    if x <= 1:
        return x
    return mod - mod // x * modinv(mod % x, mod) % mod

预计算逆元

如果需要频繁计算多个数的模逆元,可以利用以下递推公式预计算:

MOD = 10**9 + 7
inv = [0] * MOD
inv[1] = 1
for i in range(2, MOD):
    inv[i] = MOD - MOD // i * inv[MOD % i] % MOD

这样可以在 O(MOD)O(MOD) 时间内预处理所有 [1,MOD)[1, MOD) 范围内数的逆元,之后每次查询只需 O(1)O(1)


注意事项

  1. 除零错误:必须确保不尝试除以 0
  2. 取模后的零:要注意在应用模运算后,一个非零数可能会变成零,因此在对非常数值进行除法运算时要格外小心
  3. 性能:由于计算模逆元需要 O(logp)O(\log p) 的时间,在循环内部频繁使用除法会显著增加程序的运行时间。如果多次使用同一个数的模逆元,预先计算它是更好的选择

Modular Arithmetic 模运算
https://mingsm17518.github.io/2026/04/30/算法学习/05_Gold/01_Math/02_Modular Arithmetic 模运算/
作者
Ming
发布于
2026年4月30日
更新于
2026年9月13日
许可协议