Last update:
June 10, 2026
二进制快速幂
二进制快速幂也称平方求幂。对于非负整数 ,它只需 次乘法就能计算 ,而朴素方法需要 次乘法。
这种技巧也广泛应用于与普通算术无关的任务,因为它适用于任何满足结合律的运算:
最直接的例子是模乘和矩阵乘法,下面还会讨论其他应用。
算法
朴素地计算 的 次幂,就是将 个 相乘,执行 次乘法:。但当 或 很大时,这种方式不实用。
利用 ,以及 。
二进制快速幂的思路是:根据指数的二进制表示拆分计算。
把 写成二进制,例如:
正整数 的二进制表示恰有 位。因此,如果已经知道 ,只需执行 次乘法。
现在只需找到快速求出这些幂的方法。方法很简单:序列中的每一项都是前一项的平方。
因此,计算 时,只需将其中三项相乘; 中对应 的位为 0,所以跳过它:。
总时间复杂度为 :计算这些 的幂需要对数级次数的操作,再至多用对数级次数的乘法将它们组合为最终结果。
下面的递归形式表达了同一个思路:
实现
先看递归实现,它直接对应上面的递推公式:
long long binpow(long long a, long long b) {
if (b == 0)
return 1;
long long res = binpow(a, b / 2);
if (b % 2)
return res * res * a;
else
return res * res;
}
第二种方法不使用递归。在循环中计算各个幂,只将 的对应二进制位为 1 的项乘入结果。两种方法的复杂度相同,但迭代实现省去了递归调用开销,实际运行通常更快。
long long binpow(long long a, long long b) {
long long res = 1;
while (b > 0) {
if (b & 1)
res = res * a;
a = a * a;
b >>= 1;
}
return res;
}
应用
高效计算模意义下的大整数幂
问题:计算 。这是一种非常常见的运算,例如求模乘逆元时就会用到。
解法:取模与乘法相容:。因此可以直接沿用上述代码,将每次乘法改为模乘:
long long binpow(long long a, long long b, long long m) {
a %= m;
long long res = 1;
while (b > 0) {
if (b & 1)
res = res * a % m;
a = a * a % m;
b >>= 1;
}
return res;
}
注意:当指数 远大于 时,还能进一步加速。对于正整数 ,若 ,则当 为素数时,有 ;当 为合数时,有 。这些结论直接来自费马小定理和欧拉定理,详情参见“模逆元”文章。
高效计算斐波那契数
问题:计算第 个斐波那契数 。
解法:详细说明见“斐波那契数”文章,这里只概述算法。由于 ,计算下一个数只需前两个数。可以构造一个 矩阵,表示从 到 的变换。例如,应用到 后就得到 。将这个变换矩阵提升到 次幂,就能在 时间内得到 。
将一个置换应用 次
问题:给定长度为 的序列,将一个给定置换应用 次。
解法:用二进制快速幂计算置换的 次幂,再将它应用到序列上。时间复杂度为 。
vector<int> applyPermutation(vector<int> sequence, vector<int> permutation) {
vector<int> newSequence(sequence.size());
for(int i = 0; i < sequence.size(); i++) {
newSequence[i] = sequence[permutation[i]];
}
return newSequence;
}
vector<int> permute(vector<int> sequence, vector<int> permutation, long long k) {
while (k > 0) {
if (k & 1) {
sequence = applyPermutation(sequence, permutation);
}
permutation = applyPermutation(permutation, permutation);
k >>= 1;
}
return sequence;
}
注意:这个问题还可以在线性时间内解决:构造置换图,分别处理其中每个环,将 对环长取模,即可确定环内每个元素的最终位置。
快速将一组几何变换应用到一组点
问题:给定 个点 ,对每个点执行 个变换。每个变换可以是平移、缩放,或绕指定轴旋转指定角度。此外还有“循环”操作,将一组变换重复 次,循环还可以嵌套。要求比 更快地执行所有变换,其中 是展开循环后的变换总数。
解法:先观察不同变换如何改变坐标:
- 平移:分别给各个坐标加上一个常数。
- 缩放:分别将各个坐标乘以一个常数。
- 旋转:变换较复杂,这里不展开推导,但每个新坐标仍能表示成原坐标的线性组合。
这些变换都可以通过坐标上的线性运算来表示。使用齐次坐标后,一个变换可以写成如下 矩阵:
让原坐标与一个值为 1 的附加坐标组成向量,再与矩阵相乘,就得到新坐标及值为 1 的附加坐标:
为什么要引入第四个虚构坐标?这正是齐次坐标的妙处,它在计算机图形学中应用广泛。平移需要给坐标加常数;若不升维,就无法用一次矩阵乘法表达这样的仿射运算。升维后,仿射变换就成为线性变换。
下面给出几种变换的矩阵表示:
- 平移: 坐标增加 , 坐标增加 , 坐标增加 。
- 缩放: 坐标乘以 ,另两个坐标各乘以 。
- 旋转:遵循右手定则,绕 轴逆时针旋转 度。
将每个变换写成矩阵后,变换序列就是这些矩阵的乘积;重复 次的循环就是矩阵的 次幂,可以在 次矩阵乘法内用快速幂求出。先在 时间内计算代表所有变换的矩阵,再用 时间将其应用到所有点,总复杂度为 。
图中长度为 的路径数量
问题:给定一个有 个顶点的有向无权图,求从任意顶点 到任意顶点 、长度为 的路径数量。
解法:另有专文详细讨论此问题。算法是将邻接矩阵 提升到 次幂:若存在从 到 的边,则 ,否则为 0。结果矩阵的 就是从 到 、长度为 的路径数量。该解法的时间复杂度为 。
注意:同一篇文章还讨论了带权变体:寻找恰好包含 条边的最小权重路径。它同样可以通过邻接矩阵的幂求解。矩阵中保存从 到 的边权;没有边则为 。矩阵乘法也需修改:将乘法换成加法,将求和换成取最小值,即 。
快速幂的变体:计算两个数的模乘
问题:计算 。、 各自可以放入内置数据类型,但它们的乘积超出了 64 位整数范围。目标是不使用任意精度整数运算得到结果。
解法:沿用上面的二进制构造方法,只把乘法换成加法。这样将一次乘法展开成 次加法与乘二操作;乘二本质上也是加法。
注意:也可以利用浮点运算。先用浮点数计算 ,将结果转换为无符号整数 。再用无符号整数运算从 中减去 ,最后对 取模。这个方案看上去不太可靠,但非常快,也容易实现;详情见原文相关链接。
练习题
- UVa 1230 – MODEX
- UVa 374 – Big Mod
- UVa 11029 – Leading and Trailing
- Codeforces – Parking Lot
- leetcode – Count good numbers
- Codechef – Chef and Riffles
- Codeforces – Decoding Genome
- Codeforces – Neural Network Country
- Codeforces – Magic Gems
- SPOJ – The last digit
- SPOJ – Locker
- LA – 3722 Jewel-eating Monsters
- SPOJ – Just add it
- Codeforces – Stairs and Lines
-
Contributors:
- jakobkogler (41.57%)
- gabrielsimoes (17.98%)
- tcNickolas (11.24%)
- back2sqr1 (5.99%)
- RodionGork (5.62%)
- Electron1997 (3.0%)
- adamant-pwn (2.25%)
- shark_s (2.25%)
- Morass (1.87%)
- turfaa (1.87%)
- roundspecs (1.5%)
- wangwillian0 (0.75%)
- spike1236 (0.37%)
- mukeshdhadhariya (0.37%)
- harsh-1806 (0.37%)
- arjunUpatel (0.37%)
- snymm2 (0.37%)
- PAndaContron (0.37%)
- rskbansal (0.37%)
- zhangdavid275 (0.37%)
- ahmedibrahim404 (0.37%)
- algmyr (0.37%)
- vatsalsharma376 (0.37%)











暂无评论内容