1. 为什么信息学竞赛选手必须掌握快速幂?
在信息学竞赛的赛场上,时间就是生命。当你在NOIP、IOI等比赛中遇到需要计算a^b mod p的问题时,如果还在用for循环一次次相乘,那等待你的很可能就是TLE(时间限制 exceeded)。我参加省级选拔赛时就曾因为这个问题痛失20分——题目要求计算3^1000000007 mod 1e9+7,我的朴素算法直接超时。
快速幂算法能在O(logb)的时间复杂度内完成幂运算,比朴素算法的O(b)快了几个数量级。这个差距在b=1e18时尤为明显:朴素算法需要1万亿次循环,而快速幂只需60次左右的计算。这就是为什么所有竞赛选手的武器库里都必须有这个"核武器"。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 快速幂的核心原理与数学基础
2.1 幂运算的二分思想
快速幂的精妙之处在于将指数进行二进制分解。举个例子,计算5^13时:
- 13的二进制是1101,即8+4+0+1
- 因此5^13 = 5^8 × 5^4 × 5^1
这种分解使得我们不需要进行13次乘法,而是通过不断平方来构建各个分量:
code复制5^1 = 5
5^2 = (5^1)^2 = 25
5^4 = (5^2)^2 = 625
5^8 = (5^4)^2 = 390625
最终结果 = 390625 × 625 × 5 = 1220703125
2.2 模运算下的优化
在竞赛中更常见的是带模数的快速幂(a^b mod p)。这里有个关键性质:
code复制(a × b) mod p = [(a mod p) × (b mod p)] mod p
这意味着我们可以在每次乘法后立即取模,避免数值溢出。以计算3^100 mod 7为例:
code复制3^1 mod 7 = 3
3^2 mod 7 = (3×3) mod 7 = 2
3^4 mod 7 = (2×2) mod 7 = 4
3^8 mod 7 = (4×4) mod 7 = 2
3^16 mod 7 = (2×2) mod 7 = 4
3^32 mod 7 = (4×4) mod 7 = 2
3^64 mod 7 = (2×2) mod 7 = 4
100 = 64 + 32 + 4 → 结果 = (4×2×4) mod 7 = 32 mod 7 = 4
3. 快速幂的标准实现与细节优化
3.1 递归版实现(教学用)
cpp复制long long quickPow(long long a, long long b, long long p) {
if (b == 0) return 1 % p;
long long half = quickPow(a, b/2, p);
if (b % 2 == 0)
return half * half % p;
else
return half * half % p * a % p;
}
这个版本直观体现了分治思想,但递归调用会有额外的栈开销,在竞赛中不推荐实际使用。
3.2 迭代版实现(竞赛标准)
cpp复制typedef long long LL;
LL fastPow(LL a, LL b, LL p) {
LL res = 1 % p;
while (b) {
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
关键点解析:
- 初始化res=1%p:处理了p=1的特殊情况
- b & 1:等价于b%2,但位运算更快
- a的平方和res的更新都立即取模:防止溢出
- b>>=1:等价于b/=2,位运算优化
3.3 防溢出技巧
当p接近1e9时,两个1e9级别的数相乘可能超过long long范围(约9e18)。这时需要快速乘:
cpp复制LL fastMul(LL a, LL b, LL p) {
LL res = 0;
while (b) {
if (b & 1) res = (res + a) % p;
a = (a + a) % p;
b >>= 1;
}
return res;
}
然后在fastPow中用fastMul替代乘法。这是处理大数模幂的标准方法。
4. 竞赛中的典型应用场景
4.1 组合数取模
计算C(n,k) mod p时,当p是质数可以用卢卡斯定理:
code复制C(n,k) ≡ C(n/p,k/p) × C(n%p,k%p) mod p
其中C(n%p,k%p)需要计算阶乘的逆元,这时就需要快速幂:
code复制inv(a) = a^(p-2) mod p
4.2 矩阵快速幂
斐波那契数列的第n项可以用矩阵快速幂在O(logn)时间内求出:
code复制[F(n) ] = [1 1]^(n-1) [F(1)]
[F(n-1)] [1 0] [F(0)]
实现时只需把fastPow中的数乘法替换为矩阵乘法即可。
4.3 模方程求解
解a^x ≡ b mod p这类离散对数问题时,Baby-step Giant-step算法中需要大量使用快速幂。这类问题常出现在省选及以上级别的比赛中。
5. 实战中的常见错误与调试技巧
5.1 数据范围陷阱
我曾在一场比赛中写出这样的代码:
cpp复制int fastPow(int a, int b, int p) { ... }
当a=1e9, p=1e9+7时,a*a直接溢出int导致WA。必须用long long!
5.2 边界条件测试
必须测试的边界情况:
- a=0, b=0, p=1
- a=1, b=1e18, p=1
- a=1e9, b=1e9, p=1e9+7
5.3 性能优化对比
测试数据:计算1e18次方模1e9+7
- 朴素算法:>1000秒(理论值)
- 递归快速幂:约1μs
- 迭代快速幂:约0.5μs
实际比赛中,迭代版比递归版通常快2-3倍,这也是为什么迭代版是标准实现。
6. 快速幂的扩展应用
6.1 快速幂的快速幂
当需要计算a^(b^c) mod p时,根据费马小定理:
code复制a^(b^c) mod p = a^(b^c mod (p-1)) mod p (当p是质数且a,p互质)
这需要先对指数b^c用快速幂取模,再用结果做快速幂。
6.2 多进制快速幂
除了二进制分解,还可以用三进制、五进制等。例如:
code复制a^13 = a^(9+3+1) = a^9 × a^3 × a^1
这在某些特殊情况下可能更优,但二进制版本仍然是通用性最好的选择。
6.3 快速幂与哈希
在字符串哈希中,我们经常需要计算base^len mod p。预处理这些幂值可以优化多次查询的性能:
cpp复制const int MAXN = 1e6+5;
LL pow_base[MAXN];
void init() {
pow_base[0] = 1;
for(int i=1; i<MAXN; i++)
pow_base[i] = pow_base[i-1] * BASE % MOD;
}
7. 竞赛选手的训练建议
- 模板背诵:把标准迭代版快速幂背到能3分钟内默写正确
- 变式训练:尝试实现快速乘、矩阵快速幂等变种
- 题目积累:收集20道以上快速幂相关题目进行分类训练
- 基础模幂计算
- 组合数学应用
- 矩阵快速幂应用
- 数学定理结合(费马小定理、欧拉定理等)
- 错误日志:记录自己犯过的溢出、边界错误,比赛前复习
我个人的训练方法是每次比赛前必做3道快速幂题目热身,保持手感。这个习惯让我在省队选拔中成功AC了一道需要快速幂+逆元的关键题。
