快速幂

快速幂把指数拆成二进制:当前位是 `1` 就乘进答案,底数每轮自乘一次。

一句话算法

快速幂把指数拆成二进制:当前位是 1 就乘进答案,底数每轮自乘一次。

问题模型

给定整数 a,b,ma,b,m,要求快速计算:

abmodm a^b \bmod m

如果直接做 bb 次乘法,时间复杂度是 O(b)O(b)。当 bb 达到 10910^9 或更大时,这个做法不可接受。

快速幂利用指数的二进制表示,把乘法次数降到 O(logb)O(\log b)

核心直觉

任意非负整数 bb 都能唯一写成若干个不同的 22 的幂之和。例如:

13=8+4+1=23+22+20 13 = 8 + 4 + 1 = 2^3 + 2^2 + 2^0

所以:

a13=a8a4a1 a^{13}=a^8\cdot a^4\cdot a^1

而这些 a2ia^{2^i} 可以通过不断平方得到:

a1, a2, a4, a8, a^1,\ a^2,\ a^4,\ a^8,\dots

因此我们只需要从低位到高位扫描 bb 的二进制:

  • 当前二进制位是 1:把当前 base 乘进答案。
  • 每扫描一位:base = base * base,进入下一位的权值。

“口诀”

是 1 就乘,base 自乘,指数右移。

算法步骤

  1. ans = 1 % mod
  2. base = base % mod
  3. exp > 0
    • 如果 exp & 1,说明当前最低位是 1,执行 ans = ans * base % mod
    • 执行 base = base * base % mod
    • 执行 exp >>= 1,删掉已经处理的最低位。
  4. 返回 ans

算法证明

核心不变量:每一轮开始时,尚未处理的指数部分仍然由 exp 表示,已经处理且需要保留的幂已经乘进 ans

设原指数为:

b=c020+c121++ck2k b = c_0 2^0 + c_1 2^1 + \cdots + c_k 2^k

其中 ci{0,1}c_i\in\{0,1\}

  1. ii 轮时,base 等于:

    a2i a^{2^i}
  2. 若第 iici=1c_i=1,答案中必须包含这一项,所以乘入:

    ansansa2i ans \leftarrow ans \cdot a^{2^i}
  3. 若第 iici=0c_i=0,答案中不需要这一项,跳过即可。

  4. 每一轮后:

    (a2i)2=a2i+1 (a^{2^i})^2=a^{2^{i+1}}

    所以 base = base * base 正确进入下一位。

所有二进制位处理完后,ans 中正好乘入了所有 ci=1c_i=1 的项,因此:

ans=abmodm ans=a^b \bmod m

算法正确。

复杂度分析

  • 时间复杂度:O(logb)O(\log b)
  • 空间复杂度:O(1)O(1)

如果 base * base 可能超过 long long,需要使用 __int128 或快速乘。

代码实现

cpp
        
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <bits/stdc++.h> using namespace std; using ll = long long; // 计算 base^exp mod mod,要求 mod > 0。 ll quick_pow(ll base, ll exp, ll mod) { ll ans = 1 % mod; base %= mod; while (exp > 0) { if (exp & 1) ans = ans * base % mod; base = base * base % mod; exp >>= 1; } return ans; } int main() { ll base, exp, mod; cin >> base >> exp >> mod; cout << quick_pow(base, exp, mod) << "\n"; return 0; }

测试用例

输入:

2 13 1000

输出:

192

解释:

213=8192,8192mod1000=192 2^{13}=8192,\quad 8192\bmod 1000=192

应用分类详解

快速幂的本质是:把“重复乘很多次”变成“按二进制选择若干个平方项”。只要题目出现大指数、重复合成、幂取模,就应该想到快速幂。

一、大指数幂取模

典型模式: 计算 abmodma^b\bmod m,其中 bb 很大。

识别信号: 题面出现“幂”“取模”“指数很大”“b109b\le 10^9 或更大”。

核心建模: 用二进制拆指数,每一位只处理一次。

应用场景 经典题目 核心思路
模板计算 luogu-P1226 快速幂直接计算幂取模
大指数取模 LeetCode 50 不取模时同样使用二进制拆指数

二、数论公式中的幂

典型模式: 公式里包含 aka^k,但最终只关心模意义下的结果。

识别信号: 出现“费马小定理”“逆元”“组合数取模”“矩阵快速幂前置知识”。

核心建模: 把公式中的幂计算交给快速幂。

应用场景 经典题目 核心思路
费马逆元 luogu-P3811 质数模下 a1ap2(modp)a^{-1}\equiv a^{p-2}\pmod p
组合数取模 luogu-P3807 Lucas 或阶乘逆元常用快速幂

三、重复操作的快速合成

典型模式: 一个操作需要重复执行 bb 次,且操作可以合并。

识别信号: 出现“重复执行很多次”“函数复合”“状态转移很多步”。

核心建模: 把乘法换成更一般的“合并操作”。如果操作满足结合律,就可以按二进制倍增。

应用场景 经典题目 核心思路
矩阵快速幂 luogu-P1962 把数乘换成矩阵乘
置换快速幂 luogu-P3390 重复变换可按二进制合成

经典例题

1. luogu-P1226

快速幂模板题。直接套用 quick_pow(base, exp, mod)

2. luogu-P3811

线性逆元是更优解,但快速幂求单个逆元是必须掌握的基础方法。

3. luogu-P1962

斐波那契数列的矩阵快速幂做法。本质仍然是“指数二进制拆分”,只是乘法对象从整数变成矩阵。

参考

  • 旧版文章:Rbook_ejs_old/book/math/quick_pow/index.md