快速幂是一个在 O ( log n ) O(\log n) O(logn) 的时间内计算 a n a^n an 的小技巧,而暴力的计算需要 O ( n ) O(n) O(n) 的时间。
计算
a
a
a 的
n
n
n 次方表示将
n
n
n 个
a
a
a 乘在一起:
a
n
=
a
×
a
⋯
×
a
⏟
n
个 a
a^{n} = \underbrace{a \times a \cdots \times a}_{n\text{ 个 a}}
an=n 个 a
a×a⋯×a 。然而当
a
,
n
a,n
a,n 太大的时侯,这种方法就不太适用了。不过我们知道:
a
b
+
c
=
a
b
⋅
a
c
,
a
2
b
=
a
b
⋅
a
b
=
(
a
b
)
2
a^{b+c} = a^b \cdot a^c,a^{2b} = a^b \cdot a^b = (a^b)^2
ab+c=ab⋅ac,a2b=ab⋅ab=(ab)2 。二进制取幂的想法是,我们将取幂的任务按照指数的 二进制表示 来分割成更小的任务。
首先我们将
n
n
n 表示为 2 进制,举一个例子:
3 13 = 3 ( 1101 ) 2 = 3 8 ⋅ 3 4 ⋅ 3 1 3^{13} = 3^{(1101)_2} = 3^8 \cdot 3^4 \cdot 3^1 313=3(1101)2=38⋅34⋅31
因为
n
n
n 有
⌊
log
2
n
⌋
+
1
\lfloor \log_2 n \rfloor + 1
⌊log2n⌋+1 个二进制位,因此当我们知道了
a
1
,
a
2
,
a
4
,
a
8
,
…
,
a
2
⌊
log
2
n
⌋
a^1, a^2, a^4, a^8, \dots, a^{2^{\lfloor \log_2 n \rfloor}}
a1,a2,a4,a8,…,a2⌊log2n⌋ 后,我们只用计算
O
(
log
n
)
O(\log n)
O(logn) 次乘法就可以计算出
a
n
a^n
an 。
于是我们只需要知道一个快速的方法来计算上述
3
3
3 的
2
k
2^k
2k 次幂的序列。这个问题很简单,因为序列中(除第一个)任意一个元素就是其前一个元素的平方。比如说为了计算
3
13
3^{13}
313,我们只需要将对应二进制位为 1 的整系数幂乘起来就行了。
这个算法的复杂度是
O
(
log
n
)
O(\log n)
O(logn) 的,我们计算了
O
(
log
n
)
O(\log n)
O(logn) 个
2
k
2^k
2k 次幂的数,然后花费
O
(
log
n
)
O(\log n)
O(logn) 的时间选择二进制为 1 对应的幂来相乘。
简单来说,思路就是:(二分的思想)
1)当b是奇数时,那么有
a
b
=
a
∗
a
b
−
1
a^b = a * a^{b-1}
ab=a∗ab−1
2)当b是偶数时,那么有
a
b
=
a
b
/
2
∗
a
b
/
2
a^b = a^{b/2} * a^{b/2}
ab=ab/2∗ab/2
代码实现
首先我们可以直接按照上述递归方法实现:
long long quickpow(long long a, long long b)
{
if (b == 0)
return 1;
long long res = quickpow(a, b / 2);
if (b % 2)
return res * res * a;
else
return res * res;
}
第二种实现方法是非递归式的。它在循环的过程中将二进制位为 1 时对应的幂累乘到答案中。尽管两者的理论复杂度是相同的,但第二种在实践过程中的速度是比第一种更快的。
long long quickpow(long long a, long long b)
{
long long res = 1;
while (b)
{
if (b & 1)
res = res * a;
a = a * a;
b >>= 1;
}
return res;
}