从一道题引出一个算法:斐波那契数列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$换成其特征方程的两根,而后再各类部分相应修改便可。这里就很少讲了。留做读者自行练习