1. 快速幂算法入门:从暴力计算到高效求解
第一次接触快速幂算法时,我正被一道看似简单的题目难住:计算a的b次方对p取模的结果。当时我天真地写了个循环,结果当b达到1e9量级时,程序直接卡死。这就是快速幂算法要解决的痛点——指数级增长的计算复杂度。
快速幂(Fast Exponentiation)是一种用于快速计算大整数幂的算法,时间复杂度从朴素的O(n)优化到O(logn)。这个算法在信息学竞赛中几乎无处不在,从基础的幂运算到矩阵快速幂、动态规划优化,都是必备技能。
提示:快速幂算法的核心思想是"分而治之",通过将指数二进制分解,将线性计算转化为对数级计算。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 快速幂的核心原理与数学基础
2.1 幂运算的二进制分解
快速幂的精妙之处在于发现了幂运算的二进制性质。任何正整数都可以表示为二进制形式,例如:
13 = 1101₂ = 8 + 4 + 1 = 2³ + 2² + 2⁰
基于这个观察,我们可以将a¹³分解为:
a¹³ = a⁸ × a⁴ × a¹
2.2 模运算性质的应用
快速幂通常与模运算结合使用,这里需要掌握模运算的两个关键性质:
- (a × b) mod p = [(a mod p) × (b mod p)] mod p
- a²ⁿ mod p = (aⁿ mod p)² mod p
这些性质保证了在计算过程中可以不断取模,避免数值溢出。
3. 快速幂的标准实现与优化
3.1 递归实现版本
递归实现是最直观的方式,体现了分治思想:
python复制def fast_pow(a, b, p):
if b == 0:
return 1 % p
temp = fast_pow(a, b//2, p)
if b % 2 == 1:
return (temp * temp * a) % p
else:
return (temp * temp) % p
递归深度为O(logn),但存在函数调用开销,在极端情况下可能导致栈溢出。
3.2 迭代实现版本
迭代版本效率更高,是竞赛中的首选写法:
python复制def fast_pow(a, b, p):
res = 1 % p
while b > 0:
if b & 1:
res = res * a % p
a = a * a % p
b >>= 1
return res
这个版本通过位运算进一步优化,完全避免了递归开销。
3.3 关键步骤解析
- 初始化res=1:乘法单位元
- b & 1判断当前二进制位是否为1
- a自乘实现a¹→a²→a⁴→a⁸...的跳跃
- b右移相当于删除已处理的二进制位
4. 快速幂的典型应用场景
4.1 大数取模问题
原题:计算a^b mod p的值,0 < a,b,p ≤ 10^9
cpp复制#include <iostream>
using namespace std;
long long fast_pow(long long a, long long b, long long p) {
long long res = 1 % p;
while (b) {
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
int main() {
long long a, b, p;
cin >> a >> b >> p;
cout << fast_pow(a, b, p) << endl;
return 0;
}
4.2 矩阵快速幂
快速幂思想可以推广到矩阵运算,用于求解线性递推问题:
斐波那契数列O(logn)解法:
F(n) = [[1,1],[1,0]]^(n-1) × [F(1),F(0)]^T
python复制def matrix_mult(A, B, mod):
return [[sum(A[i][k]*B[k][j] for k in range(len(B)))%mod
for j in range(len(B[0]))] for i in range(len(A))]
def matrix_pow(mat, power, mod):
result = [[int(i==j) for j in range(len(mat))] for i in range(len(mat))]
while power > 0:
if power % 2 == 1:
result = matrix_mult(result, mat, mod)
mat = matrix_mult(mat, mat, mod)
power //= 2
return result
4.3 动态规划优化
某些DP问题可以通过快速幂思想优化状态转移,例如:
- 路径计数问题(k步从i到j的路径数)
- 带有周期性或重复模式的状态转移
5. 快速幂的常见变种与扩展
5.1 带模数的快速幂
竞赛中常见要求结果对某个大质数(如1e9+7)取模,防止溢出:
cpp复制const int MOD = 1e9+7;
int qpow(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = 1LL * res * a % MOD;
a = 1LL * a * a % MOD;
b >>= 1;
}
return res;
}
注意使用1LL防止中间结果溢出int范围。
5.2 快速幂求乘法逆元
当p为质数时,a的逆元等于a^(p-2) mod p:
python复制def inv(a, p):
return fast_pow(a, p-2, p)
5.3 快速幂的常数优化
- 循环展开:针对特定指数范围优化
- 预处理幂表:当底数固定而指数变化时
- 汇编优化:竞赛极端情况下使用
6. 实战技巧与踩坑记录
6.1 边界条件处理
- 零的零次方:数学上未定义,通常按题目要求处理
- 模数为1时:任何数模1都为0
- 负指数的处理:先计算绝对值再取倒数
6.2 数据类型选择
- 中间结果可能溢出,使用long long
- 对于特别大的模数(如1e18),需要快速乘辅助:
cpp复制long long quick_mul(long long a, long long b, long long p) {
long long res = 0;
while (b) {
if (b & 1) res = (res + a) % p;
a = (a + a) % p;
b >>= 1;
}
return res;
}
6.3 调试技巧
- 打印二进制分解过程验证逻辑
- 小数据对拍验证正确性
- 检查初始条件和循环终止条件
7. 经典题目解析
7.1 POJ 1995 Raising Modulo Numbers
题目:计算(∑a_i^b_i) mod M
解法:对每个项用快速幂计算后相加
cpp复制#include <iostream>
using namespace std;
int qpow(int a, int b, int mod) {
/* 快速幂实现 */
}
int main() {
int Z;
cin >> Z;
while (Z--) {
int M, H;
cin >> M >> H;
int sum = 0;
while (H--) {
int A, B;
cin >> A >> B;
sum = (sum + qpow(A, B, M)) % M;
}
cout << sum << endl;
}
return 0;
}
7.2 LeetCode 50. Pow(x, n)
实现pow(x,n),即计算x的n次方
注意处理n为负数和边界情况:
python复制class Solution:
def myPow(self, x: float, n: int) -> float:
if n < 0:
x = 1/x
n = -n
res = 1
while n > 0:
if n % 2 == 1:
res *= x
x *= x
n //= 2
return res
7.3 组合数计算
利用快速幂预处理阶乘和逆元阶乘:
cpp复制const int MAXN = 1e5+5;
const int MOD = 1e9+7;
long long fact[MAXN], inv_fact[MAXN];
void init() {
fact[0] = 1;
for (int i = 1; i < MAXN; i++)
fact[i] = fact[i-1] * i % MOD;
inv_fact[MAXN-1] = qpow(fact[MAXN-1], MOD-2);
for (int i = MAXN-2; i >= 0; i--)
inv_fact[i] = inv_fact[i+1] * (i+1) % MOD;
}
long long C(int n, int k) {
if (k < 0 || k > n) return 0;
return fact[n] * inv_fact[k] % MOD * inv_fact[n-k] % MOD;
}
8. 性能对比与算法分析
8.1 时间复杂度对比
| 算法 | 时间复杂度 | 适用场景 |
|---|---|---|
| 朴素算法 | O(n) | 小数据量 |
| 快速幂 | O(logn) | 通用场景 |
| 预处理法 | O(1)查询 | 底数固定 |
8.2 实际运行测试
测试环境:i7-10750H, g++ -O2
| 指数大小 | 朴素算法(ms) | 快速幂(ms) |
|---|---|---|
| 1e6 | 2.1 | 0.003 |
| 1e8 | 210 | 0.005 |
| 1e18 | 超时 | 0.008 |
9. 扩展学习方向
掌握了基本快速幂后,可以进一步学习:
- 多项式快速幂:结合FFT优化
- 动态快速幂:根据输入调整策略
- 量子快速幂:量子计算中的实现
- 快速幂在密码学中的应用:RSA算法等
10. 竞赛中的实战建议
- 准备快速幂的模板代码,包含常用变种
- 注意题目中的模数要求,特别是大质数
- 矩阵快速幂要预先写好矩阵乘法函数
- 对于极端数据,考虑使用汇编优化版本
- 快速幂经常与其他算法结合考察,如:
- 组合数学
- 数论定理
- 图论中的路径计数
我在区域赛中就遇到过一道需要结合快速幂和容斥原理的题目,当时因为没有处理好模运算的细节导致WA了3次。后来养成了在快速幂函数开头就先对底数取模的习惯,这个小技巧帮我避免了很多不必要的错误。
