從一道題引出一個算法:斐波那契數列html
這道題並無什麼花裏胡哨的條件,就是很簡單的計算$F(n)\ mod\ p$。c++
可是這題的$n$達到了$10^{30000000}$級別,很顯然不能直接用矩陣快速冪作。算法
所以咱們要引入一個概念:斐波那契循環節。函數
顯而易見的是經過看題解咱們知道,斐波那契數列在模一個數時會出現循環,而這個週期的長度就稱爲斐波那契循環節。ui
因此咱們只須要求出斐波那契循環節$m$,而後用矩陣快速冪計算$F(n\ mod\ m)\ mod\ p$就好了。this
下面將會介紹如何計算斐波那契循環節。spa
(由於這只是介紹文並不是論文,不免有不嚴謹之處,敬請諒解。本文以通俗易懂爲前提,夾有較爲詳細的證實。)code
參考自一篇全英文的paperThe Period of the Fibonacci Sequence Modulo jhtm
\(Tips:\)blog
$1.$本文的數學推導具備必定難度,部分知識涉及到了高等代數和初等數論,不知道的東西能夠上網查,若是實在不懂能夠暫時跳過,由於這並不會影響到閱讀其餘部分。
$2.$若是不想看證實過程,能夠直接跳到第五部分看結論。
直接看個人另外一篇博客吧。二次剩餘
有一個非空集合$G$,和在$G$上的一個二元運算$\cdot:G\times G\mapsto G$。
(通常咱們叫這個運算乘法,其運算結果爲積)
它們構成了一個代數系統\(\{G,\cdot\}\),這個代數系統知足:
$1.\(封閉性:\)\forall a,b\in G,a\cdot b\in G$。
$2.\(結合律:\)\forall a,b,c\in G,(a\cdot b)\cdot c=a\cdot(b\cdot c)$。
那麼咱們將其稱爲半羣。
若是這個半羣還知足
$3.\(單位元:\)\exists e\in G,\forall a\in G,e\cdot a=a\cdot e=a$。這個$e$稱做單位元,或者稱做$1$。(\(a^0=1\))
$4.\(逆元:\)\forall a\in G,\exists b\in G,a\cdot b=b\cdot a=e$。這個$b$稱做$a$的逆元,記作$a^{-1}$。(逆元具備惟一性)
那麼咱們將其稱爲羣。
若是這個羣還知足
$5.\(交換律:\)\forall a,b\in G,a\cdot b=b\cdot a$。
那麼咱們稱這個羣爲交換羣或阿貝爾羣。
在阿貝爾羣中這個二元運算常常被稱做加法,其運算結果爲和,這個運算記作$+$。
此時該羣的單位元常常被稱做零元,記做$0$。$a$逆元的逆元常常被記做$-a$。
(這些東西能夠聯想實數的加法和乘法)
設${G,+,\cdot}$是一個代數系統($G$是非空集合,$+\(和\)\cdot$爲二元運算即加法和乘法),若
$1.{G,+}$是一個交換羣。
$2.{G,\cdot}$是一個半羣。
$3.\(分配率:\)\forall a,b,c\in G,a\cdot(b+c)=(a\cdot b)+(a\cdot c),(a+b)\cdot c=(a\cdot c)+(b\cdot c)$。
那麼咱們將其稱爲環。
若是這個環知足
$4.{G\setminus{0},\cdot}$是一個交換羣。
那麼咱們將其稱爲域。(實際上第四條性質中把$0$去掉就只是由於在域中$0$不具備乘法逆元)
百度百科特徵方程,包會。特徵方程
令$\phi=\frac{1+\sqrt5}{2},\overline{\phi}=\frac{1-\sqrt5}{2}\(,易知有\)\phi^2=\phi+1$與$\overline{\phi}^2=\overline{\phi}+1$。
實際上這就是斐波那契數列的特徵方程$x^2=x+1$的兩實數根。
而且由此咱們能夠推出斐波那契數列的通項公式
模$p$意義下的斐波那契數列$F_n(mod\ p)$的循環節$m$是使得$F_m\equiv0(mod\ p)\bigwedge F_{m+1}\equiv1(mod\ p)$的最小正整數$m$。
易知$m|k\Leftrightarrow F_k\equiv0(mod\ p),F_{k+1}\equiv1(mod\ p)$。
咱們須要先證實一些引理。(或者說是與結論直接關係不大的定理)
設$p$爲素數,$n$爲正整數,若$a\equiv1(mod\ p)$,則$a^{pn}\equiv1(mod\ p{n+1})$。
證:
咱們使用數學概括法。
不妨設$a=rp+1(r\in\mathbb Z)$。
運用二項式定理,
所以當$n=1$時引理成立。
假設$n=m$時定理成立,即有$a^{pm}\equiv1(mod\ p{m+1})$。
不妨設$a^{pm}=1+sp{m+1}(s\in\mathbb Z)$。
一樣運用二項式定理,
所以當$n=m$成立時,$n=m+1$也成立。
引理$1$得證。
設$p$爲素數,$k$爲正整數,$m$爲$F_n(mod\ p)\(的循環節,有\)\phi^{mp^}\equiv\overline{\phi}{mp}\equiv1(mod\ p^k)$
證:
咱們知道有
用$\phi^m$替換$\overline{\phi}^m$獲得
運用引理$1$,
推論$1$得證。
設$p$爲$5$之外的素數,那麼$(\frac5p)=1\Leftrightarrow p\equiv\pm1(mod\ 5),(\frac5p)=-1\Leftrightarrow p\equiv\pm2(mod\ 5)$
(勒讓德符號,前置知識中有講解)
證:
運用二次互反律,
運用勒讓德符號的定義式,
而後把$1$到$4$代進去算就能夠證實了。(其實是懶得打了)
如今進入中心環節的證實。
這一部分的定理與斐波那契循環節有着更強的聯繫,實際上將求斐波那契循環節一點點分解而後解決。
設$P=\prod\limits_{i=1}p_i$,$m_i$爲$F_n(mod\ p_i^)$的循環節,$M$爲$F_n(mod\ P)$的循環節,則$M=lcm(m_1,\cdots,m_s)$。
證:
定理$1$得證。
設$p$爲素數,$m$是$F_n(mod\ p)$的循環節,$M$是$F_n(mod\ pk)$的循環節,則$M| mp$。
證:
由**推論$1$**咱們有
定理$2$得證。
設$p$爲素數,$m$爲$F_n(mod\ p)$的循環節,若$p\equiv\pm1(mod\ 5)$,則$m| p-1$
證:
由**引理$2$**知$5$是$p$的二次剩餘,因此能夠直接開方($\sqrt5$在模$p$的域$\mathbb$中)。
運用費馬小定理,
定理$3$得證。
設$p$爲素數,$m$爲$F_n(mod\ p)$的循環節,若$p\equiv\pm2(mod\ 5)$,則$m|2p+2\bigwedge 2\nmid\frac{2p+2}$
證:
由引理2知$5$不是是$p$的二次剩餘,所以不能夠直接開方($\sqrt5$在不在模$p$的域$\mathbb$中)。
因此咱們定義$\mathbb(\sqrt5)={a+b\sqrt5|a,b\in\mathbb}$。
這裏能夠理解爲相似於從實數域擴展到複數域,而後咱們能夠證實它知足域的特徵。
(證實在附錄中)
利用二項式定理,咱們有
同理,咱們能夠獲得$\overline{\phi}^p=\phi$。
所以咱們有
又有
綜上,
定理$4$得證。
實際上計算斐波那契循環節的方式已經在第四部分徹底給出,下面作一下總結。
由定理1,分解質因數$P=\prod\limits_{i=1}s{p_i}$,求$F_n(mod\ p_i^)$的循環節$M_i$,而後取$M=lcm(M_1,\cdots,M_s)$
如今咱們將問題轉化成了求$F_n(mod\ p_i^)$的循環節$M_i$。
由定理2,$F_n(mod\ p_i^)$的循環節$M_i$是$p_i^$乘$F_n(mod\ p_i)$的循環節$m_i$的積$m_ip_i^$的某一因數。
一個個檢查因數會很麻煩,直接取這個$m_ip_i^$也沒有任何問題。由於咱們是爲了快速計算斐波那契數列的某個值,而$m_ip_i^$是$M_i$一個倍數,最後獲得的"循環節"$\overline M=lcm(m_1p_1^,\cdots,m_sp_s^)$也就會相應地變成正確的循環節$M$的倍數。可是$\overline M$必定是斐波那契數列循環的週期之一。(相似於三角函數最小正週期與週期的關係)。並且這個值也不會很大$(long\ long$範圍內$)$,對其取模而後跑矩陣快速冪徹底沒有問題。
對於每一個$p_i$,若是$p_i\le5$,直接打表:$2$的最小循環節是$3,3$的最小循環節是$8,5$的最小循環節是$20$。(這個都手算便可)
若$p_i\equiv\pm1(mod\ 5)$,則$m_i$是$p_i-1$的某一因數,理由同上,直接取自己便可。
若$p_i\equiv\pm2(mod\ 5)$,則$m_i$是$2p+2$的某一因數且$2\nmid\frac{2p+2}$,直接取自己亦可。
關於上面那個不過重要的證實,這裏進行補充。
設集合$G={0,1,\cdots,p-1}$($p$是素數),並對其定義模$p$意義下的加法$+\(和乘法\)\cdot$,易證其是一個域,記爲$\mathbb F_p$。
其零元爲$0$,單位元爲$1$,$a$的加法逆元爲$p-1-a$,除$0$之外的數的乘法逆元必定存在。
首先證實$\mathbb F_p(\sqrt5)$對於加法是一個交換羣。
封閉性:
任取
易知
因而有
則
結合律:仿照證實封閉性易證不難。
零元:易知$0+0\sqrt5$是零元。
逆元:$a+b\sqrt5$的逆元爲$(p-1-a)+(p-1-b)\sqrt5$。
交換律:仿照證實封閉性易證不難。
因此$\mathbb F_p(\sqrt5)$對於加法是一個交換羣。
而後咱們證實$\mathbb F_p(\sqrt5)$去掉零元以後對於乘法是一個交換羣。
封閉性:仿照證實對於加法有封閉性易證不難。
結合律:仿照證實對於加法有結合律易證不難。
零元:易知$1+0\sqrt5$是零元。
逆元:這個是最很差作的,對於$A=a+b\sqrt5$,咱們先取一個$B=a-b\sqrt5$,這兩個乘起來是一個整數$a2-5b2$,顯然這個數不爲$0$,咱們再把$B$的兩個係數$a$和$b$都除以這個數就好了。
交換律:仿照證實對於加法有交換律易證不難。
因此$\mathbb F_p(\sqrt5)$去掉零元后對於乘法是一個交換羣。
所以$\mathbb F_p(\sqrt5)$是一個域。
(幾乎徹底同樣的過程要寫十遍我人都傻了,本身證一下吧,都看到這了相信你必定能證實的)
注意特判模數爲$1$的狀況。
#include<bits/stdc++.h> #define LL long long using namespace std; const int N=1e4,S=30000001; char s[S]; LL pi[N],k[N],P; inline LL gcd(register LL n,register LL m){while(m^=n^=m^=n%=m); return n;} inline LL lcm(register LL n,register LL m){return n/gcd(n,m)*m;} struct matrix { LL a[3][3]; matrix(){memset(a,0,sizeof(a));} matrix operator*(matrix x) { matrix s; for(register int i=1;i<=2;i++) for(register int j=1;j<=2;j++) for(register int k=1;k<=2;k++) s.a[i][j]=(s.a[i][j]+a[i][k]*x.a[k][j])%P; return s; } matrix operator^(register LL k) { matrix s=*this,e; e.a[1][1]=e.a[2][2]=1; for(;k;k>>=1,s=s*s) if(k&1) e=e*s; return e; } }; inline int power(register LL n) { matrix a,ans; a.a[1][1]=a.a[1][2]=a.a[2][1]=1,a.a[2][2]=0; ans=a^n; return ans.a[1][2]; } inline LL Get(register LL p) { register int s=sqrt(p),tot=0; for(register int i=2;i<=s;++i) if(!(p%i)) { pi[++tot]=i,k[tot]=1; while(!(p%i)) p/=i,k[tot]*=i; } for(register int i=1;i<=tot;++i) k[i]/=pi[i]; if(p!=1) k[++tot]=1,pi[tot]=p; for(register int i=1;i<=tot;++i) if(pi[i]==2) k[i]*=3; else if(pi[i]==3) k[i]*=5; else if(pi[i]==5) k[i]*=20; else if(pi[i]%5==1||pi[i]%5==4) k[i]*=pi[i]-1; else k[i]*=(pi[i]+1)<<1; register LL ans=k[1]; for(register int i=2;i<=tot;++i) ans=lcm(ans,k[i]); return ans; } int main() { register int len; register LL m,n=0; scanf("%s%lld",s+1,&P),len=strlen(s+1); if(P==1) return cout<<0,0; m=Get(P); for(register int i=1;i<=len;++i) n=((n<<3)+(n<<1)+(s[i]^48))%m; if(!n) return cout<<0,0; if(n==1) return cout<<1,0; return cout<<power(n),0; }
那個英文論文裏面已經講了另一個數列$Lucas$數列,能夠去看看練練手。
實際上大多數二階線性遞推數列均可以這樣作,具體方法就是把$\phi$和$\overline\phi$換成其特徵方程的兩根,而後再各類部分相應修改便可。這裏就很少講了。留做讀者自行練習