快速幂和矩阵快速幂(取模)算法

2019-04-14 08:11发布

对于普通类型的求a^n,我们的求法是不是a*a*a*a....,这样乘以n次,时间复杂度为O(n),对于普通n比较小的我们可以接受,然而当n比较大的时候,计算就慢了,所以我们就去寻找更快捷的计算方法!

例如:我们要求2^8,我们通过当为偶数的时候,a^n=(a*a)^(n/2),当n为奇数时,a^n=a*(a*a)^(n/2)的形式,是不是可以转化为4^4->8^2->64^1,就可以了,2^5的话2*4^2->2*16^1。(把幂化为底数,减少乘法的次数)

int power(int a, int n)//a^n { int ans = 1; while(n > 0) { if(n&1) //当n为奇数时,乘以余下的一个a ans *= a; a *= a; n /= 2; } return ans; }

但是当n比较大时往往我们的结果都非常大,如果直接算的话就会溢出,所以我们在计算的过程中会对结果取模,然后我们就有了快速幂取模算法。控制数据的大小,对于取模我们有(a*b)%c = ((a%c)*(b%c))%c,给出代码。 

int power(long long a, int n) { long long ans = 1; while(n > 0) { if(n&1) { ans *= a; ans %= mod; } a *= a%mod; a %= mod; n /= 2; } return ans%mod; }

下面是矩阵快速幂,区别只是底数换成了矩阵

const int N=10; int n,mod; int temp[N][N]; int res[N][N],a[N][N]; void mul(int a[][N],int b[][N])//矩阵乘法 { memset(temp,0,sizeof(temp)); for(int i=0;i>=1;//幂次/2 } return ; }

应用如下:

我们从最简单的斐波那契数列入手,求菲波那切数列的第n项,n很大,所以MOD1e9+7

斐波的为前两项之和,即发f[0]=1,f[1]=1,f[i] = f[i-1]+f[i-2](i>1)

比较简单的可以直接得到递推式

 

,进一步递推

由于矩阵乘法满足结合律,所它也可以用快速幂算法求解。

然后给一些简单的递推式:
1.f(n)=a*f(n-1)+b*f(n-2)+c;(a,b,c是常数)

2.f(n)=c^n-f(n-1) ;(c是常数)

简单的例子

Fibonacci数列

考虑Fibonacci数列,  F(n)=F(n-1)+F(n-2)
将右边两项看做是一个列向量的形式,令   X_{n-1}=left{egin{matrix}F_{n-1}\F_{n-2}end{matrix}
ight}
很容易得到Xn的形式,即  X_{n}=left{egin{matrix}F_{n}\F_{n-1}end{matrix}
ight}
现在的任务就是找到一个系数矩阵A,使得AX_{n-1}=X_n,且A需与n无关。 
如果能够找到这个n,则易知A^{n-1}X_1=X_n,于是可以利用矩阵快速幂计算出Xn。这样就可以在O(logn)的时间内计算出指定的Fibonacci数。 
这个矩阵很容易找,观察易得  A=left{egin{matrix}1&1\1& 0end{matrix}
ight}	ag{1}  

Fibonacci数列变种

推广一下,如果令F_n=aF_{n-1}+bF_{n-2},则系数矩阵为  A=left{egin{matrix}a&b\1& 0end{matrix}
ight}	ag{2}  

利用二项式展开构造矩阵

计算k次方和

Fibonacci数列只是最简单的例子,对于稍微复杂一点的例子,二项式展开是常用的一项技术。 
考虑计算:  S_n=sum_{i=1}^n{i^k}
易得  Sn=n^k+Sn-1
如果仿照Fibonacci数列,令  X_{n-1}=left{egin{matrix}n^k\S_{n-1}end{matrix}
ight}
则  X_{n}=left{egin{matrix}(n+1)^k\S_{n}end{matrix}
ight}
此时,可求出  A=left{egin{matrix}left({frac{n+1}{n}}
ight)^{k}&&0\1&&1end{matrix}
ight}
这个系数矩阵很明显是不能用的,因为它与n有关,无法将递推公式转化为矩阵的幂运算。 
这里需要使用二项式展开。  S_n=(n-1+1)^k+S_{n-1}\=C_k^0(n-1)^k+C_k^1(n-1)^{k-1}+cdots+C_k^k+S_{n-1}
此时,如果令  X_{n-1}=left{egin{matrix}(n-1)^k\(n-1)^{k-1}\vdots\(n-1)^0\S_{n-1}end{matrix}
ight}
则有  X_{n}=left{egin{matrix}n^k\n^{k-1}\vdots\n^0\S_{n}end{matrix}
ight}
现在的任务与刚才一样,找到一个系数矩阵A,使得AXn-1=Xn,且A需与n无关。 
有关SnSn的递推公式实际上就指明了AA的最后一行。而利用二项式展开很容易得到其他行。 
有  A=left{egin{matrix}C_k^0&&C_k^1&&cdots&&C_k^k&&0\0&&C_{k-1}^0&&cdots&&C_{k-1}^{k-1}&&0\vdots&&vdots&&ddots&&vdots&&vdots\0&&cdots&&cdots&&C_0^0&&0\C_k^0&&C_k^1&&cdots&&C_k^k&&1end{matrix}
ight}	ag{3}
最后有  A^{n-1}X_1=X_n
且  X_1=left{1space1spacecdotsspace1
ight}^T
利用该系数矩阵可以在O(k3logn)O(k3log⁡n)时间内计算出kk次方的和。  

更一般的例子

再考虑一个一般情况: S_n=sum_{i=1}^n{(ai+b)^k}
与刚才几乎一模一样,有  S_n=[a(n-1)+(a+b)]^k+S_{n-1}\=C_k^0a^k(n-1)^k+C_k^1a^{k-1}(a+b)(n-1)^{k-1}+cdots+C_k^k(a+b)^k+S_{n-1}
剩下的步骤,也几乎完全与之前的一样,令  X_{n-1}=left{egin{matrix}(n-1)^k\(n-1)^{k-1}\vdots\(n-1)^0\S_{n-1}end{matrix}
ight}
则  X_{n}=left{egin{matrix}n^k\n^{k-1}\vdots\n^0\S_{n}end{matrix}
ight}
同样,有关SnSn的递推公式实际上就指明了AA的最后一行,其他行则利用二项式展开得到。 
有  A=left{egin{matrix}C_k^0&&C_k^1&&cdots&&C_k^k&&0\0&&C_{k-1}^0&&cdots&&C_{k-1}^{k-1}&&0\vdots&&vdots&&ddots&&vdots&&vdots\0&&cdots&&cdots&&C_0^0&&0\C_k^0a^k&&C_k^1a^{k-1}(a+b)&&cdots&&C_k^k(a+b)^k&&1end{matrix}
ight}	ag{4}  

更复杂的例子

假设要计算如下和式:  S_n=sum_{i=1}^ni^kk^i
同样,将其写成递推公式,有  S_n=n^kk^n+S_{n-1}\=(n-1+1)^kk^{(n-1)+1}+S_{n-1}\=C_k^0(n-1)^kk^{(n-1)+1}+C_k^1(n-1)^{k-1}k^{(n-1)+1}+cdots+C_k^kk^{(n-1)+1}+S_{n-1}
将与n有关的项抽出来作为列向量,令  X_{n-1}=left{egin{matrix}(n-1)^kk^{(n-1)+1}\(n-1)^{k-1}k^{(n-1)+1}\vdots\(n-1)^0k^{(n-1)+1}\S_{n-1}end{matrix}
ight}
则  X_{n}=left{egin{matrix}n^kk^{n+1}\n^{k-1}k^{n+1}\vdots\n^0k^{n+1}\S_{n}end{matrix}
ight} 同样,有关Sn的递推公式说明了系数矩阵AA的最后一行。而其他行仍然可以由二项式展开得到,只是相差了一个系数k而已,这很容易解决。 
最后,有  A=left{egin{matrix}kC_k^0&&kC_k^1&&cdots&&kC_k^k&&0\0&&kC_{k-1}^0&&cdots&&kC_{k-1}^{k-1}&&0\vdots&&vdots&&ddots&&vdots&&vdots\0&&cdots&&cdots&&kC_0^0&&0\C_k^0&&C_k^1&&cdots&&C_k^k&&1end{matrix}
ight}	ag{5}