快速幂
首先,快速幂是用来解决什么问题的?
给定三个正整数a、b、m(a<109,b<1018,1<m<109),求ab%m
对于这个问题,如果使用暴力来处理显然是不行的,O(b)的复杂度支持b<108都已经很艰难了,更何况1018
快速幂的做法是基于二分的思想,因此也常称为二分幂。快速幂基于一下事实:
- 如果b是奇数,那么有ab = a*ab-1
- 如果b是偶数,那么有ab = ab/2*ab/2
显然,b是奇数的情况总可以在下一步转换为b是偶数的情况,而b是偶数的情况总可以在下一步转换为b/2的情况。这样,在log(b)级别次数的转换后,就可以把b变为0,而任何正整数的0次都是1。
快速幂的递归方法:
这显然是递归的思想,于是可以得到快速幂的递归写法,时间复杂度为O( l o g b logb logb)
typedef long long LL;
//求a^b%m,递归思想
LL binaryPow(LL a,LL b,LL m)
{
if(b==0) //如果b为0,那么a^0 = 1;
return 1%m; //这里最好加上%m,如果a = 1,b = 0,m = 1时,应该返回的是0
//如果b为奇数,转换为b-1
if(b%2==1)
return a*binaryPow(a,b-1,m)%m;
else{
LL mul = binaryPow(a,b/2,m);
return mul*mul%m;
}
}
注意:条件if(b%2 == 1)可以用if(b&1)代替。这是应因为b&1进行位与操作,判断b的的末位是否为1,因此当b为奇数时b&1返回1,if条件成立。这样写执行速度会更快。
快速幂的迭代方法:
对于ab来说,如果把b写成二进制,那么b就可以写成若干二次幂之和。例如13的二进制是1101,于是3号位、二号位、一号位都是1,那么就可以得到13 = 23+22+20=8+4+1,所以a13=a8+4+1=a8*a4*a1
通过以上推导,我们可以发现a13可以表示为a8、a4、a1的乘积。很容易想象,通过同样的推导,我们可以把任意的ab表示成a2k、……、a8、a4、a2、a1中若干项的乘积,其中如y果b的二进制i号位为1,那么项a2i就被选中。于是可以得到计算ab的大致思路:令i从0到k枚举b的二进制的每一位,如果当前位为1,那么累积a2i。注意到序列a2k、……、a8、a4、a2、a1的前一项总是等于后一项的平方,因此具体实现的时候可以这么做:
- 初始令ans等于1,用来存放累积的结果。
- 判断b的二进制末尾是否为1(判断b&1是否为1,也可以理解为判断b是否为奇数),如果是,令ans乘上a的值。
- 令a平方,并将右移一位(也可以理解为将b除以2)。
- 只要b大于0,就返回步骤2.
快速幂迭代写法:
typedef long long LL;
LL binaryPow(LL a,LL b,LL m)
{
LL ans = 1;
while(b>0)
{
if(b&1) //如果b二进制末尾为1
{
ans = ans*a%m; //令ans累积上a
}
a = a * a % m;
b>>=1; //将b右移1位,或b = b/2
}
return ans;
}
在实际的应用中递归的写法和迭代的写法在效率上的差别不那么明显,可以自己随意选择。
模板题:P1177 【模板】快速排序
矩阵快速幂
矩阵快速幂,即处理给定 n × n n \times n n×n 的矩阵 A,求 AK这么一类问题。首先我们得先了解矩阵乘法。
矩阵乘法
矩阵乘法如下所示:
M 1 = ( A B C D E F ) M_1 = \left( \begin{matrix} A & B & C \\ D & E & F \end{matrix} \right) M1=(ADBECF)
M 2 = ( a b c d e f ) M_2 = \left( \begin{matrix} a & b \\ c & d \\ e & f \end{matrix} \right) M2=⎝⎛acebdf⎠⎞
M 1 × M 2 = ( A × a + B × c + C × e A × b + B × d + C × f D × a + E × c + F × e D × b + E × d + F × f ) M_1 \times M_2 = \left( \begin{matrix} A \times a+B \times c+C \times e & A\times b+B \times d+C \times f \\ D\times a + E\times c+F\times e & D\times b+E\times d + F\times f \end{matrix} \right) M1×M2=(A×a+B×c+C×eD×a+E×c+F×eA×b+B×d+C×fD×b+E×d+F×f)
代码如下:
const int N=100;
int c[N][N];
void multi(int a[][N],int b[][N],int n)//n是矩阵大小,n<N
{
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
c[i][j]+=a[i][k]*b[k][j];
}
很显然这里的矩阵乘法的时间复杂度是O(n3),如果我们要计算一个矩阵的幂这个时间复杂度一定是非诚大的,所以这里我们使用矩阵的快速幂进行计算。
这里我们只需要把快速幂计算中的乘法改为矩阵乘法即可。
为了使代码结构清晰,我们构造一个矩阵结构体,对其中的乘法有运算符进行重载。
typedef long long LL;
const int mod = 1e9+7;
LL n,m,k;
struct Matrix{
int a[105][105];
Matrix operator * (const Matrix &b)
{
Matrix ret;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
{
ret.a[i][j]=0;
for (int k=1;k<=n;k++)
ret.a[i][j]=((LL)a[i][k]*b.a[k][j]%mod+ret.a[i][j]%mod)%mod;
}
return ret;
}
}a;
//然后利用这个结构体进行矩阵快速幂计算
Matrix binaryPow(Matrix a,LL x)
{
Matrix ret,k;
k = a;
ret = a;
x--;
while(x)
{
if (x&1) ret=ret*k;
k=k*k;
x>>=1;
}
return ret;
}
模板题:P3390 【模板】矩阵快速幂