矩阵快速幂,就是矩阵乘法和快速幂的结合,因此在讲矩阵快速幂以前,先讲讲矩阵乘法和快速幂。spa
什么是矩阵?由 \(m \times n\) 个数 \(a_{i,j}(i=1,2,\cdots,m;j=1,2,\cdots,n)\) 排成的一个 \(m\) 行 \(n\) 列的数表code
被称为 \(m\) 行 \(n\) 列矩阵,简称 \(m \times n\) 矩阵。为了表示这是个总体老是加一个括弧,并用大写字母表示他,计作:blog
这个矩阵中的\(m \times n\)个数被称为矩阵\(A\)的元素,简称元。矩阵\(A\)也可被计作\(A_{m \times n}\)。ip
行矩阵(行向量): 只有一行的矩阵。
列矩阵(列向量): 只有一列的矩阵。
同型矩阵: 两个矩阵\(A\)、\(B\)的行数和列数都相等
单位矩阵: 在矩阵乘法中起着很大的做用,至关于乘法中的\(1\),她是个方阵(即行数和列数相同的矩阵),左上角到右下角的对角线(被称为主对角线)上的元素都是\(1\),除此以外都是\(0\)。以下就是一个\(4 \times 4\)的单位矩阵:get
咱们定义两个m \(\times n\)的矩阵\(A\)和\(B\)相加为:io
注意:class
咱们定义两个矩阵 \(A_{m \times s}\) 和 \(B_{s \times n}\) 相乘获得一个矩阵 \(C_{m,n}\),而且二进制
并将此计作 \(C=AB\)。
注意: 矩阵乘法不知足交换律,但知足结合律和分配律。
\(\tiny{\text{这里没放图是由于放了也看不懂(逃}}\)
若是还不理解的话能够看这里
用代码写出来就是下面这个亚子:方法
struct matrix { int n,m;//n是行数,m是列数 ll e[105][105]; }a; matrix mul(matrix a,matrix b) { matrix ans; ans.n=a.n; ans.m=b.m; for(int i=1;i<=ans.n;i++) for(int j=1;j<=ans.m;j++) ans.e[i][j]=0;//初始化 for(int i=1;i<=a.n;i++) for(int j=1;j<=b.m;j++) for(int k=1;k<=a.m;k++) ans.e[i][j]=(ans.e[i][j]+(a.e[i][k]*b.e[k][j])%mod)%mod;//矩阵乘法结果可能很大,因此要取模 return ans; }
通常咱们求 \(x^y\) 的方法是一个个乘过去,但这种方法太慢了。咱们须要一种新的,快速的方法来求,而这种方法就是快速幂。这里咱们举例来理解,就好比求 \(3^{5}\),用 \(ans\) 记录答案。\(5\) 转成二进制就是 \({(101)}_2\),那么咱们从右往左开始枚举,首先,有个1,那么咱们就记录 \(ans=ans \times 3=1 \times 3=3\),而后让 \(3\) 变成本身的平方,也就是 \(3^2=9\) ,而后接下来是个 \(0\),那就不用乘,而后再让 \(9\) 变成本身的平方 \(81\),而后又是个 \(1\),因此 \(ans=ans \times 81=3 \times 81=243\)。最后,获得 \(3^5 = 243\)。手算一下能够发现这是对的。固然 \(x^y\) 极可能是个很大的数,因此通常都会取模。
代码实现长这个亚子:im
long long power(long a,long b,long c) { long long ans=1; while(b)//没有枚举完 { if(b&1) ans=(ans*a)%c;//这一位是不是1 a=(a*a)%c; b>>=1;//走到下一位 } return ans%c; }
讲完了上面这些,那矩阵快速幂就很好理解了!就是把运算改为矩阵运算。
首先,\(ans\) 和返回值都应该是一个矩阵;而后,\(ans\) 应该初始化为单位矩阵,缘由上面已经讲了;(1.1 矩阵的定义)再而后,里面的乘法应该改为矩阵乘法。最后提醒一句,矩阵快速幂必须是个方阵,不然要两个同型矩阵(本身乘本身,固然是同型矩阵啦~)能够实现矩阵乘法是不可能的。
代码实现:
struct matrix { int n,m; ll e[105][105]; }a; matrix mul(matrix a,matrix b) { matrix ans; ans.n=a.n; ans.m=b.m; for(int i=1;i<=ans.n;i++) for(int j=1;j<=ans.m;j++) ans.e[i][j]=0; for(int i=1;i<=a.n;i++) for(int j=1;j<=b.m;j++) for(int k=1;k<=a.m;k++) ans.e[i][j]=(ans.e[i][j]+(a.e[i][k]*b.e[k][j])%mod)%mod;//快速幂的取模就在这里了! return ans; }//代码和上面的区别 matrix power(matrix a,ll b) { matrix ans; ans.n=a.n; ans.m=a.m; for(int i=1;i<=ans.n;i++) for(int j=1;j<=ans.m;j++) if(i==j) ans.e[i][j]=1; else ans.e[i][j]=0;//初始化为单元矩阵 while(b) { if(b&1) ans=mul(ans,a);//要把乘换成矩阵乘法哦! a=mul(a,a);//这里也同样呢 b>>=1; }//快速幂板子 return ans; }
先挂上神仙同窗的矩阵加速友链。
矩阵快速幂能够应用到不少递推题上。
先放出一道例题:洛谷P1962 斐波那契数列
这题看到 \(n < 2^{63}\) 就知道,直接递推确定不行,咱们就要用矩阵快速幂来加加速。
咱们知道,这题是由初始的两个数递推得来的,那么咱们有没有什么办法让他用更快的速度来递推呢?
固然是用矩阵啦~
因为递推 \(F_n\) 须要用到 \(F_{n-1},F_{n-2}\) 两个项,因此咱们就构建有两个项的初始矩阵:
而后,显而易见地,目标矩阵长这个样子:
而后,就有了一个递推式子:
可是咱们不知道这个 \(C\) 是什么呀。
不要紧,咱们来把上面的式子拆开来:
为了让结果等于 \(B\),必需要让
因而就获得了
可是,这样不仍是 \(O(n)\) 递推吗?
观察一下,能够发现每次乘的是同一个矩阵,而原来的递推每次的项是不同的。
这样有什么好处呢?咱们能够先让全部的 \(C\) 都乘起来,再乘 \(A\) 就好了。
能这样作,仍是由于矩阵乘法知足结合律。
那么要乘几个 \(C\) 呢?是 \(n-2\) 次。为何呢?由于初始矩阵中已经有了两项:\(F_1\) 和 \(F_2\)。
最终代码以下:
#include<cstdio> #define ll long long #define mod 1000000007 using namespace std; ll n; struct matrix { int n,m; ll e[105][105]; }a,b; matrix mul(matrix a,matrix b) { matrix ans; ans.n=a.n; ans.m=b.m; for(int i=1;i<=ans.n;i++) for(int j=1;j<=ans.m;j++) ans.e[i][j]=0; for(int i=1;i<=a.n;i++) for(int j=1;j<=b.m;j++) for(int k=1;k<=a.m;k++) ans.e[i][j]=(ans.e[i][j]+(a.e[i][k]*b.e[k][j])%mod)%mod; return ans; } matrix power(matrix a,ll b) { matrix ans; ans.n=a.n; ans.m=a.m; for(int i=1;i<=ans.n;i++) for(int j=1;j<=ans.m;j++) if(i==j) ans.e[i][j]=1; else ans.e[i][j]=0; while(b) { if(b&1) ans=mul(ans,a); a=mul(a,a); b>>=1; } return ans; } int main() { scanf("%lld",&n); if(n<=2) { printf("1"); return 0; } a.n=2,a.m=2; a.e[1][1]=a.e[1][2]=a.e[2][1]=1; a=power(a,n-2); b.n=1,b.m=2; b.e[1][1]=b.e[1][2]=1; printf("%lld",mul(b,a).e[1][1]); return 0; }