快速幂
快速幂
一般而言,在计算\(a^n\)时,我们会使用暴力的方法,逐个去乘,这样操作时间复杂度是\(O(n)\)。
而是用快速幂算法时间复杂度可以降到\(O(logn)\)。
递归思想
在计算\(a^n\)时,我们可以先计算\(a^2\),然后在计算\((a^2)^2\),一直计算到\(n\)次幂。
例如\(3^9\),可以这样递归:
\(3^9=3*3^8\);
\(3^8=3^4*3^4\);
\(3^4=3^2*3^2\);
\(3^2=3^1*3^1\);
\(3^1=3^1*3^0\);
观察这个递归过程,我们可以发现:
当\(n\)是偶数时,\(a^n=a^{n/2}*a^{n/2}\);
当n是奇数时,\(a^n=a*a^{n/2}\);
代码如下:
long long fastPow(long long a, long long n){
if(n == 1) return a;
long long ans = fastPow(a,n/2);
if(n % 2 == 1) return ans * ans * a;
else return ans * ans;
}
它是以数的二进制为基础,利用二进制的特性进行计算;
例如:计算\(2^{11}\),我们可以把它分成\(2^8\),\(2^2\),\(2^1\)的乘积,即\(2^{11}=2^8*2^2*2^1\)。
那么现在可以思考三个问题:1、如何求\(2^8\),\(2^2\),\(2^1\)的值,2、如何把n分成\(11=8+2+1\),3、如何跳过不需要的部分。
1、如何求\(2^8\),\(2^2\),\(2^1\)的值
我们可以很容易发现\(2^1*2^1=2^2\),\(2^2*2^2=2^4\),\(2^4*2^4=2^8\)等等,都是2的倍数,并且都是倍乘关系,通过递推就可以实现。
2、如何把n分成\(11=8+2+1\)
用二进制我们便可以轻松理解,把n转换为二进制数,二进制数中的每一位的权值都是低一位的两倍,对应的\(a^i\)是倍乘关系,例如\(11_{10}=1011_2=2^3+2^1+2^0=8+2+1\)。
3、如何跳过不需要的部分
例如\(2^{11}\),\(2^{11}=2^8*2^2*2^1\),需要跳过\(2^4\),在这里我们只需要做个判断即可,利用二进制的位运算很容易:
a、n&1,去n的最后一位,并且判断这一位是否需要跳过。
b、n>>=1,把n右移一位,去掉n的最后一位。
long long fastPow(long long a, long long n){
long long ans = 1;
while(n){
if(n&1) ans *= a;
a = a* a;
n >>= 1;
}
return ans;
}
现在我们在思考一个问题当\(n\)或者\(a\)比较大时,我们的结果往往都会非常大,如果直接计算的话就会发生溢出,所以我们在计算的过程中要对结果进行取模运算。
typedef long long ll;
ll qpow(ll n, ll a, ll mod){
ll res = 1;
while(n){
if(n & 1) res = ((res % mod) * (a % mod)) % mod;
n >>= 1;
a = ((a % mod) * (a % mod)) % mod;
}
return res % mod;
}