快速幂及快速幂取余
快速幂
当遇到求 a n a^n an这种问题时,可以将指数n拆开于是变成 ( a 2 ) n / 2 (a^2)^{n/2} (a2)n/2。然后重复将n除2,底数平方,直到指数为0为止就是结果。于是解决这题的时间复杂度从 O ( n ) O(n) O(n)(普通循环相乘a的做法)到了 O ( l o g n ) O(logn) O(logn)。
这就是快速幂算法,本身还是挺简单的,主要思想就是让底数变大,指数不断缩小。
唯一要考虑的就是当n是奇数时需要将式子变成 a ∗ a n − 1 a*a^{n-1} a∗an−1,然后再n除二,底数平方(因为小数次方计算机不好计算),代码如下。
int f(int a,int n)
{
int ans=1;
while(n){
if(n%2){ //一个技巧是把n%2改成n&1也能达到一样的效果,而位运算的效率要高于求余
n-=1; //n是奇数时
ans*=a; //最后n=1的时候会从这里结束
}else{
n/=2; //n是偶数时 也有一个技巧是n/=2改成n>>=1
a*=a;
}
}
return ans;
}
可是滑稽的是因为指数爆炸,a=2 n等于64时的结果便超过了长整形的数据范围,所以这个算法并没有卵用,所以这个模板只适用于大数模拟的快速幂,或者将这个算法与快速幂求余一起讨论。
快速幂取余
当遇到求
a
n
%
b
a^n\%b
an%b这种问题时,可以利用求余的基本运算法则,基本运算法则如下:
1.
(
a
+
b
)
%
c
=
(
a
%
c
+
b
%
c
)
%
c
1.(a+b)\%c=(a\%c+b\%c)\%c
1.(a+b)%c=(a%c+b%c)%c
2.
(
a
−
b
)
%
c
=
(
a
%
c
−
b
%
c
)
%
c
2.(a-b)\%c=(a\%c-b\%c)\%c
2.(a−b)%c=(a%c−b%c)%c
3.
(
a
∗
b
)
%
c
=
(
a
%
c
∗
b
%
c
)
%
c
3.(a*b)\%c=(a\%c*b\%c)\%c
3.(a∗b)%c=(a%c∗b%c)%c
类似于普通四则运算,不过没有除法。
第三点可以容易证明
a
b
%
c
=
(
a
%
c
)
b
%
c
a^b\%c=(a\%c)^b\%c
ab%c=(a%c)b%c
利用这点和快速幂算法就可把此问题中的指数除二得到 ( ( a 2 ) n / 2 ) % b = ( ( a 2 % b ) n / 2 ) % b ((a^2)^{n/2})\%b=((a^2\%b)^{n/2})\%b ((a2)n/2)%b=((a2%b)n/2)%b (n为偶数),接下来就是和快速幂的做法一样了,将n不断缩小直至为1为止,同样也要考虑奇数,所以代码只需稍作修改如下。
int f(int a,int n,int b)
{
int ans=1;
a%=b; //防止a*a溢出
while(n){
if(n&1){
n-=1;
ans=(ans*a)%b; //只需要加个求余符号即可
}else{
n>>=1;
a=(a*a)%b;
}
}
return ans%b; //多求余一次防止n等于0时出现意外错误
}
以上便是快速幂取模,复杂度同样是
O
(
l
o
g
n
)
O(logn)
O(logn)。要注意的是
b
2
b^2
b2不能超出数据范围,不然会溢出。
快速幂取模可以常见使用于求某一个
a
n
a^n
an的后几位数,比如
a
n
%
1000
a^n\%1000
an%1000就是得到后三位数的值。