快速幂算法详解
前言
首先考虑这么一个问题
{% note info %}
给定三个正整数 a, b, m(a < 10910^9109,b < 10910^9109,1 < m < 10910^9109),求 aba^bab % m。
{% endnote %}
对于这个问题,只要写一个简单的循环就能够搞定
// 普通求幂
long long QuickPow(long long a, long long b, long long m) {
long long ans = 1;
for (int i = 0; i < b; i++) {
ans = ans * a % m;
}
return ans;
}
然而,当 a, b 到达一定值时,最终的结果会非常大,对于这个问题,O(b)的时间复杂度很难进行。
快速幂算法
快速幂,就是用效率更高(时间复杂度更低)的方法求幂,可以将时间复杂度优化至 O(logn)
递归快速幂
快速幂算法的关键在于对指数 b 的处理,我们很容易得到如下事实:
若 b 为奇数,则ab=a×ab−1a^b=a\times a^{b-1}ab=a×ab−1
若 b 为偶数,则ab=ab/2×ab/2a^b=a^{b/2}\times a^{b/2}ab=ab/2×ab/2
举个例子,求 272^727:
- 由于 7 为奇数,因此有 27=2×262^7 = 2 \times 2^627=2×26
- 由于 6 为偶数,因此有 262^626 = 23×232^3 \times 2^323×23
- 由于 3 为奇数,因此有 232^323 = 2×222 \times 2^22×22
- 由于 2 为偶数,因此有 222^222 = 21×212^1 \times 2^121×21
- 由于 1 为奇数,因此有 212^121 = 2×202 \times 2^02×20
- 最后 20=12^0=120=1,然后从下往上计算即可
根据上面的方程,很容易通过二分的思想得到快速幂算法的递归版本
// 快速幂,递归写法
long long QuickPow(long long a, long long b, long long m) {
if (b == 0) return 1;
if (b % 2 == 1) // 如果 b 为奇数,转化为 b - 1
return a * QuickPow(a, b - 1, m) % m;
else { // 如果 b 为偶数,转化为 b / 2
long long mul = QuickPow(a, b / 2, m);
return mul * mul % m;
}
}
迭代快速幂
下面说明一下快速幂的迭代写法
同样的,主要还是在指数 b 的处理,以 2102^{10}210 为例
10=(1010)2=1×23+0×22+1×21+0×20 10=\left( 1010 \right) _2=1\times 2^3+0\times 2^2+1\times 2^1+0\times 2^0 10=(1010)2=1×23+0×22+1×21+0×20
210=21×23+0×22+1×21+0×20=28+0+2+0=28×22 2^{10}=2^{1\times 2^3+0\times 2^2+1\times 2^1+0\times 2^0}=2^{8+0+2+0}=2^8\times 2^2 210=21×23+0×22+1×21+0×20=28+0+2+0=28×22
可以发现,2102^{10}210 可以表示为 282^828 和 222^222 的乘积。
同样的对 aba^bab 进行推广,我们可以把 aba^bab 表示成 a2ka^{2^{k}}a2k,a2k−1a^{2^{k-1}}a2k−1,…,a8a^8a8,a4a^4a4,a2a^2a2,a1a^1a1中若干项的乘积,即:
ab=ack⋅2k×ack−1⋅2k−1×⋯×ac3⋅8×ac2⋅4×ac1⋅2×ac0⋅1 a^b=a^{c_k\cdot 2^k}\times a^{c_{k-1}\cdot 2^{k-1}}\times \cdots \times a^{c_3\cdot 8}\times a^{c_2\cdot 4}\times a^{c_1\cdot 2}\times a^{c_0\cdot 1} ab=ack⋅2k×ack−1⋅2k−1×⋯×ac3⋅8×ac2⋅4×ac1⋅2×ac0⋅1
具体来说,枚举指数 b 的每一位,若当前位为 1,则结果累计 a2ia^{2^{i}}a2i
举例如下
| a | n(binary) | ans |
|---|---|---|
| 2(1)2 | 1010 | 1 |
| 2(10)2 | 101 | 2(10)2 |
| 2(100)2 | 10 | 2(10)2 |
| 2(1000)2 | 1 | 2(10)2 * 2(1000)2 |
具体代码实现如下:
// 快速幂,迭代写法
long long QuickPow(long long a, long long b, long long m) {
long long ans = 1;
while (b > 0) {
if (b & 1) { // 若 b 的二进制末尾为 1(也可以写成 if(b % 2))
ans = ans * a % m; // ans 累加上 a
}
a = a * a % m; // a取平方
b >>= 1; // b 的二进制右移一位(也可以写成 b /= 2)
}
return ans;
}
3510

被折叠的 条评论
为什么被折叠?



