來自洛谷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