矩阵加速——斐波那契数列


来自洛谷P1962(一道看似很水的题)

斐波那契数列的通项公式是 Fn=Fn-1 + Fn-2

在一定的复杂度内可以直接递推,但是如果n太大,那么就容易T,这时候,我们就运用矩阵加速来进行优化,以减少运行时间。


 

在看矩阵加速之前,我们首先需要了解矩阵快速幂

【模板】 洛谷P3390

 

 

 

 

 首先,我们来讲一下矩阵与矩阵之间的运算。

 

1.矩阵加法:

假定有两个矩阵A,B;

一般而言,让我们进行矩阵加法的两个矩阵会是一对同型矩阵(行列数分别相等)

那么,得到的矩阵AB的第 i 行,第 j 列的元素,即为A和B中第 i 行,第 j 列的元素的加和

举个栗子:

 

 

 那么减法运算就相当于加上一个负数,与之一样。

在加(减)法中,满足:

A+B=B+A【交换律】

A+(B+C)=(A+B)+C 【结合律】

 

2.矩阵数乘

假定我们有一个矩阵A,还有一个常数 λ,那么 λ*A 就是把A中的每一个元素都乘以 λ

再举个例子:

 

 

 在矩阵的数乘中,满足如下运算规律:

设 x,y为常数

则:x(yA)= y(xA)【交换律】

     (x+y)*A = xA + yA  【分配律】

     (A+B)*x = xA + xB  【分配律】

 

3.矩阵乘法(矩阵乘矩阵)

假定我们有两个矩阵A,B。A为n*m的矩阵,B为m*k的矩阵,只有满足A的行数数等于B的列数时,才能够进行矩阵与矩阵之间的乘法运算。运算后,得到的矩阵C将会是一个n*k大小的矩阵

那么得到C矩阵的运算公式为:

 

 

 

 

 

 又双叒叕举个例子:

 

 

 

 

就是说,矩阵AB的第 i 行第 j 列元素都是由A的第 i 行的各个元素与B的第 j 列的各个元素相乘之和

需要注意的是,在矩阵与矩阵的乘法中,有如下运算法则

1.(AB)C=A(BC)【结合律】

2.(A+B)C=AC+BC   【分配律】

这里并没有交换律,因为将A和B的顺序颠倒后,不一定符合矩阵之间的运算定义(比如A:2*3 ; B:3*5),如果满足也不一定能够得到相同的结果。

 

我们在这道模板题中,因为A只是在做幂运算,并不用加入其他的递推式,我们就可以设定一个单位矩阵 L(单位矩阵的元素非0即1),使得矩阵L的第 i 行,第 i 列的元素为1,其余为0。之后可以依照快速幂的写法完成矩阵快速幂(这里用的重载运算符)

#include<iostream> #include<cstdio> #include<cstring> #include<math.h> #include<algorithm>
using namespace std; const long long mod=1000000007; struct STU //声明结构体 { long long m[110][110]; } A,I; long long n,k; STU operator * (const STU &a,const STU &b)//重载运算符 '*' { STU x; for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { x.m[i][j]=0; } } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { for(int p=1;p<=n;p++) { x.m[i][j]+=a.m[i][p]*b.m[p][j]%mod; //矩阵乘法运算 x.m[i][j]%=mod;//别忘记取mod } } } return x; } int main(void) { cin>>n>>k; for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { cin>>A.m[i][j];//输入运算矩阵 } I.m[i][i]=1;//定义单位矩阵 } while(k>0)//快速幂运算(可以用while循环,也可以用递归) { if(k%2==1) I=I*A; A=A*A; k=k>>1; } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { cout<<I.m[i][j]<<" ";//输出每一个元素 } cout<<endl; } return 0; }



既然我们解决了矩阵快速幂的问题,那么再来看一下矩阵加速——【模板】洛谷 P1939(和斐波那契数列的做法差不多)。

 

 

 对于这道题来说,直接递归毫无疑问是要T的,那么矩阵加速就是我们的首选算法,那么,问题来了,怎么设置我们的初始矩阵呢?

 在这道题中,我们需要用第n-1个数和第n-3个数来推得第n个数

那么设现有数Fn,Fn-1,Fn-2,Fn-3

那么:Fn=Fn-1*1+Fn-2*0+Fn-3*1 ;Fn-1=Fn-1*1+Fn-2*0+Fn-3*0 ; Fn-2=Fn-1*0+Fn-2*1+Fn-3*0

则矩阵为:

                    

 

 

那么这就是我们的初始矩阵。

值得注意的是,在这个矩阵中,第N次运算得到的结果为第N+1项,那么我们就需要输出第二行,第一列的元素即可。

上代码:

#include<iostream> #include<cstdio> #include<algorithm> #include<math.h> #include<cstring>
using namespace std; const long long mod=1000000007; struct STU //结构体 { long long m[110][110]; } A,I; int T,n; STU operator * (const STU &a,const STU &b)//重载运算符 { STU x; for(int i=1;i<=3;i++) { for(int j=1;j<=3;j++) { x.m[i][j]=0; } } for(int i=1;i<=3;i++) { for(int j=1;j<=3;j++) { for(int p=1;p<=3;p++) { x.m[i][j]+=a.m[i][p]*b.m[p][j]%mod; x.m[i][j]%=mod; } } } return x; } void clear() { memset(I.m,0,sizeof(I.m));//清空I矩阵 memset(A.m,0,sizeof(A.m));//清空A矩阵 for(int i=1;i<=3;i++) I.m[i][i]=1;//还原为初始状态 A.m[1][1]=A.m[1][3]=A.m[2][1]=A.m[3][2]=1; } int ksm(int k)//快速幂 { while(k>0) { if(k%2!=0) I=I*A; A=A*A; k=k>>1; } return I.m[2][1]; } int ys(int k)//判断 { if(k<=3) return 1; else return ksm(k); } int main(void) { cin>>T; for(int i=1;i<=T;i++) { cin>>n; clear();//每一次运算后都需要清空 cout<<ys(n)<<endl; } return 0; }



正题终于要来了!

 

 看过了上面的矩阵加速例题,这道题也就变得迎刃而解。

首先,我们先定义初始矩阵,因为它只有前两项为1,那么就可以用一个1*2的矩阵L来代表,即为L【1,1】

之后,我们要定义单位矩阵,由于Fn=Fn-1+Fn-2,那么单位矩阵的第一列就应该定为:【1,1】,使得Fn-1和Fn-2能够相加

之后,为了保留下Fn-1,但Fn-2已经没有用处了,那么第二列就是【1,0】。

这样,我们的单位矩阵就是:

                                                

 

 

原式也可以化为:

 

                        

到这里,我们的矩阵就构造完成了,输出时只要输出第1行第1列的元素就行了,但是,还有一个地方,那就是当我们输入N,且N大于等于3时,我们的矩阵只需要进行N-2运算,因为在本题中第1项和第二项在初始化时已经给出,进行运算时可以直接从求第三项开始。

源代码如下:

 

#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<math.h>
using namespace std; const long long mod=1000000007; struct STU//结构体 { long long m[110][110]; } A,I; long long n; STU operator * (const STU &a,const STU &b)//重载运算符 { STU x; memset(x.m,0,sizeof(x.m)); for(int i=1;i<=2;i++) { for(int j=1;j<=2;j++) { for(int k=1;k<=2;k++) { x.m[i][j]+=a.m[i][k]*b.m[k][j]%mod; x.m[i][j]%=mod; } } } return x; } void init()//初始化 { memset(I.m,0,sizeof(I.m)); memset(A.m,0,sizeof(A.m)); I.m[1][1]=I.m[1][2]=1; A.m[1][1]=A.m[1][2]=A.m[2][1]=1; } int main(void) { cin>>n; init(); if(n<=2)//第一项或第二项的情况 { cout<<1<<endl; return 0; } else //n>=3的情况 { n=n-2; while(n>0) //快速幂 { if(n%2!=0) I=I*A; A=A*A; n=n>>1; } cout<<I.m[1][1]<<endl;//输出 return 0; } } 

 

 

 

 

 

 这样,斐波那契数列的矩阵加速解法就写完啦!

QAQ


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM