定义
快速幂 (Exponentiation by squaring ,平方求幂)是一种简单而有效的小算法,它可以以O ( log n ) O(\log n) O ( log n ) 的时间复杂度计算乘方。快速幂不仅本身非常常见,而且后续很多算法也都会用到快速幂。
背景问题
例如计算一个7 10 7^{10} 7 1 0 ,有以下几种方法。
最朴素的想法,7*7=49,49*7=343,… 一步一步算,共进行了9次 乘法。
这样算无疑太慢了,尤其对计算机的CPU而言,每次运算只乘上一个个位数,无疑太屈才了。这时我们想到,也许可以拆分问题。
先算7的5次方,即7*7*7*7*7,再算它的平方,共进行了5次 乘法。
但这并不是最优解,因为对于“7的5次方”,我们仍然可以拆分问题。
先算7*7得49,则7的5次方为49*49*7,再算它的平方,共进行了4次 乘法。
模仿这样的过程,我们得到一个在 O ( log n ) O(\log n) O ( log n ) 时间内计算出幂的算法,也就是快速幂。
具体实现
递归快速幂
用上面的背景问题可以得到下面这个递归公式:
a n = { a n − 1 × a , n i s o d d a n 2 × a n 2 , n i s e v e n b u t n o t 0 1 , n = 0 a^n = \begin{cases}
a^{n-1}\times a, & n\ is\ odd \\
a^{\frac{n}{2}}\times a^{\frac{n}{2}}, & n\ is\ even \ but \ not \ 0 \\
1,&n=0
\end{cases} a n = ⎩ ⎪ ⎨ ⎪ ⎧ a n − 1 × a , a 2 n × a 2 n , 1 , n i s o d d n i s e v e n b u t n o t 0 n = 0
依据这个递归公式,可以写出递归版本的快速幂。
1 2 3 4 5 6 7 8 9 10 int qpow (int a,int n) { if (n == 0 ){ return 1 ; }else if (n%2 == 1 ){ return qpow (a,n-1 )*a; }else { int temp=qpow (a,n/2 ); return temp*temp; } }
代码中有一个temp变量,暂存a n 2 a^{\frac{n}{2}} a 2 n 的值,这样就能避免计算两次。
非递归快速幂
虽然递归版本很容易写,但是会产生额外的空间开销。因此可以把递归版本的改写为循环版本的。
将7 10 7^{10} 7 1 0 转化为y 2 × 7 8 y^2 \times 7^8 y 2 × 7 8 。
1 2 3 4 5 6 7 8 9 10 11 int qpow (int a, int n) { int ans = 1 ; while (n){ if (n&1 ){ ans *= a; } a *= a; n >>= 1 ; } return ans; }
如果加上取模过程:
有( a ∗ b ) % c = ( ( a % c ) ∗ ( b % c ) ) % c (a*b)\%c = ((a\%c)*(b\%c))\%c ( a ∗ b ) % c = ( ( a % c ) ∗ ( b % c ) ) % c
1 2 3 4 5 6 7 8 9 10 11 12 13 int qpow (int a, int n) { int ans = 1 ; while (n){ if (n&1 ){ ans *= a; ans %= mod; } a *= a%mod; a %= mod; n >>= 1 ; } return ans; }
感觉有点类似计算2 ∗ 10 2*10 2 ∗ 1 0 时转换为2 ∗ 8 + 2 ∗ 2 = = ( 2 < < 3 ) + ( 2 < < 1 ) 2*8+2*2==(2<<3)+(2<<1) 2 ∗ 8 + 2 ∗ 2 = = ( 2 < < 3 ) + ( 2 < < 1 ) 。( 10 D = = 1010 B ) (10D==1010B) ( 1 0 D = = 1 0 1 0 B )
矩阵快速幂
其实,在算 a n a^n a n 时,只要a的数据类型支持乘法 且满足结合律 ,快速幂的算法都是有效的。矩阵、高精度整数,都可以照搬这个思路。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 template <typename T>T qpow (T a, ll n) { T ans = 1 ; while (n) { if (n & 1 ) ans = ans * a; n >>= 1 ; a = a * a; } return ans; }
矩阵快速幂的一个经典应用是求斐波那契数列:P1962 斐波那契数列
设矩阵
A = [ 0 1 1 1 ] A=\begin{bmatrix}
0&1\\
1&1
\end{bmatrix}
A = [ 0 1 1 1 ]
我们有:
A [ F n F n + 1 ] = [ F n + 1 F n + F n + 1 ] = [ F n + 1 F n + 2 ] A\begin{bmatrix}
F_n\\F_{n+1}
\end{bmatrix}=\begin{bmatrix}
F_{n+1}\\F_n+F_{n+1}
\end{bmatrix}=\begin{bmatrix}
F_{n+1}\\F_{n+2}
\end{bmatrix}
A [ F n F n + 1 ] = [ F n + 1 F n + F n + 1 ] = [ F n + 1 F n + 2 ]
于是 :
[ F n F n + 1 ] = A [ F n − 1 F n ] = A 2 [ F n − 2 F n − 1 ] = ⋯ = A n − 1 [ F 1 F 2 ] = A n − 1 [ 1 1 ] \begin{bmatrix}
F_n\\F_{n+1}
\end{bmatrix}=
A\begin{bmatrix}
F_{n-1}\\F_{n}
\end{bmatrix}=A^2\begin{bmatrix}
F_{n-2}\\F_{n-1}
\end{bmatrix}=\cdots=A^{n-1}\begin{bmatrix}
F_1\\F_{2}
\end{bmatrix}=A^{n-1}\begin{bmatrix}
1\\1
\end{bmatrix}
[ F n F n + 1 ] = A [ F n − 1 F n ] = A 2 [ F n − 2 F n − 1 ] = ⋯ = A n − 1 [ F 1 F 2 ] = A n − 1 [ 1 1 ]
这样,我们把原来较为复杂的问题转化成了求某个矩阵的幂 的问题,这就可以应用快速幂求解了。
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <cstdio> #define MOD 1000000007 typedef long long ll;struct matrix { ll a1, a2, b1, b2; matrix (ll a1, ll a2, ll b1, ll b2) : a1 (a1), a2 (a2), b1 (b1), b2 (b2) {} matrix operator *(const matrix &y) { matrix ans ((a1 * y.a1 + a2 * y.b1) % MOD, (a1 * y.a2 + a2 * y.b2) % MOD, (b1 * y.a1 + b2 * y.b1) % MOD, (b1 * y.a2 + b2 * y.b2) % MOD) ; return ans; } }; matrix qpow (matrix a, ll n) { matrix ans (1 , 0 , 0 , 1 ) ; while (n) { if (n & 1 ){ ans = ans * a; } a = a * a; n >>= 1 ; } return ans; } int main () { ll x; matrix M (0 , 1 , 1 , 1 ) ; scanf ("%lld" , &x); matrix ans = qpow (M, x - 1 ); printf ("%lld\n" , ans.a1 % MOD); return 0 ; }
参考资料
[1] https://zhuanlan.zhihu.com/p/95902286
[2] https://blog.csdn.net/qq_40061421/article/details/82625338