51 nod 1195 斐波那契数列的循环节

2019-04-13 13:19发布

51 nod 1195 斐波那契数列的循环节

题目来源: Spoj 基准时间限制:1 秒 空间限制:131072 KB 分值: 640 难度:8级算法题 斐波那契数列Mod 一个数N会得到一个新的数列,根据同余可以得知,这个数列中的数会出现循环。例如:
F (mod 4) = 0 1 1 2 3 1 …
F (mod 5) = 0 1 1 2 3 0 3 3 1 4 0 4 4 3 2 0 2 2 4 1 … 后面的数会出现循环,因此Fib Mod 4的循环节长度是6,Fib Mod 5的循环节长度是20。
给出一个数N,求斐波那契数列Mod N的循环节的长度。 Input 第1行:一个数T,表示后面用作输入测试的数的数量。(1 <= T <= 10000) 第2 - T + 1行:每行1个数N。(1 <= N <= 10^9) Output 共T行,每行一个数,对应循环节的长度。 Input示例 2
4
5 Output示例 6
20 按照唐老师题解写的,交了20几次,一直出现超时的问题,这题写了几天,断断续续的了解知识点和解决复杂度的问题。
以下是唐老师题解:/*
建议先了解一下二次剩余方面的知识,再读一下这篇论文,其中讲了一些关于斐波那契数列模意义下循环节的性质。
Charles W. Campbell II. The Period of the Fibonacci Sequence Modulo j. 2007. 主要用到几个性质:
1. 对于与5互质的质数p,如果5是模p的二次剩余,那么模p意义下的循环节长度是(p-1)的因子。
2. 对于与5互质的质数p,如果5是模p的二次非剩余,那么模p意义下的循环节长度是(2p+2)的因子。
3. 对于模质数的幂p^k意义下的循环节,其值为模p意义下的循环节长度乘p^(k-1)。
4. 对于模x意义下的循环节,如果x被质因数分解为p1^k1*p2^k2*…*pm^km,则循环节是模每个质数的幂意义下的循环节的最小公倍数。 所以算法流程就是将N分解为质数的幂的乘积,分别求解,对于模5的循环节可以预先算出,因为它必然是20的因子。
对于每个模质数p的情况,枚举循环节的倍数((p-1)或(2p+2))的因子,计算出最小循环节。 需要一些技巧将时间复杂度简化。
1. 将一些取模的操作用加减法代替;输入输出优化。
2. 筛出一些质数,加快质因数分解的操作,例如分解n的复杂度可以由O(sqrt(n))变成O(sqrt(n)/ln(n))。
3. 算模质数p意义下的循环节时,由于斐波那契数列的”循环节的倍数”次项在模意义下相等,所以可以用类似计算欧拉函数的形式将时间复杂度由O(sqrt(n))变成O(log(n))。
4. 斐波那契数列是线性递推数列,利用2*2的矩阵进行快速幂,每次的矩阵乘法是8次加法、8次乘法,但是使用二元组线性表示后,每次的二元组乘法是3次加法、4次乘法,可以节省一半时间,采用的公式可以看作是Fib(n+m)=Fib(n-1)*Fib(m)+Fib(n)*Fib(m+1)。
5. 利用记忆化搜索技巧减少小质数情况的计算。
其中,使用2、3项就已经可以在允许的时间范围内得到正确答案了,不过结合1、2、4、5也可以卡常数蹭过(主要是4)。*/
代码的注释 我就不多加了,唐老师的算法很清楚了,代码自己理解吧。 #include #include #include #include using namespace std; typedef long long LL; const LL maxn=1e5+5; LL dp[maxn*10]; LL prime[maxn],s=0; bool vis[maxn]; void init_prime(){ for(LL i=2;iif(!vis[i]){ prime[s++]=i; } for(LL j=0;j<s&&i*prime[j]*prime[j]]=1; if(i%prime[j]==0){ break; } } } return ; } LL pow_mod(LL a1,LL b1){ LL ans=1; while(b1){ if(b1&1){ ans=ans*a1; } b1>>=1; a1*=a1; } return ans; } LL pow_mod2(LL a,LL b,LL p){ LL ans=1; while(b){ if(b&1){ ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } LL gcd(LL a,LL b){ return b?gcd(b,a%b):a; } bool f(LL n,LL p){ if(pow_mod2(n,(p-1)>>1,p)!=1){ return false; } return true; } struct matrix{ LL x1,x2,x3,x4; }; matrix matrix_a,matrix_b; matrix M2(matrix aa,matrix bb,LL mod){ matrix tmp; tmp.x1=(aa.x1*bb.x1%mod+aa.x2*bb.x3%mod)%mod; tmp.x2=(aa.x1*bb.x2%mod+aa.x2*bb.x4%mod)%mod; tmp.x3=(aa.x3*bb.x1%mod+aa.x4*bb.x3%mod)%mod; tmp.x4=(aa.x3*bb.x2%mod+aa.x4*bb.x4%mod)%mod; return tmp; } matrix M(LL n,LL mod){ matrix a,b; a=matrix_a;b=matrix_b; while(n){ if(n&1){ b=M2(b,a,mod); } n>>=1; a=M2(a,a,mod); } return b; } /*bool g(matrix t){ return t.m[0][0]==1&&t.m[0][1]==0&&t.m[1][0]==0&&t.m[1][1]==1; }*/ LL fac[100][2],l,x,fs[1000]; void dfs(LL count,LL step){ if(step==l){ fs[x++]=count; return ; } LL sum=1; for(LL i=0;i1];i++){ sum*=fac[step][0]; dfs(count*sum,step+1); } dfs(count,step+1); } LL solve2(LL p){ if(p<1e6&&dp[p]){ return dp[p]; } bool ok=f(5,p);//判断5是否为p的二次剩余 LL t; if(ok){ t=p-1; } else{ t=2*p+2; } l=0; for(LL i=0;i<s;i++){ if(prime[i]>t/prime[i]){ break; } if(t%prime[i]==0){ LL count=0; fac[l][0]=prime[i]; while(t%prime[i]==0){ count++;t/=prime[i]; } fac[l++][1]=count; } } if(t>1){ fac[l][0]=t; fac[l++][1]=1; } x=0; dfs(1,0);//求t的因子 sort(fs,fs+x); for(LL i=0;i<x;i++){ matrix m1=M(fs[i],p); if(m1.x1==m1.x4&&m1.x1==1&&m1.x2==m1.x3&&m1.x2==0){ if(p<1e6){ dp[p]=fs[i]; } return fs[i]; } } } LL solve(LL n){ LL ans=1,cnt; for(LL i=0;i<s;i++){ if(prime[i]>n/prime[i]){ break; } if(n%prime[i]==0){ LL count=0; while(n%prime[i]==0){ count++;n/=prime[i]; } cnt=pow_mod(prime[i],count-1); cnt*=solve2(prime[i]); ans=(ans/gcd(ans,cnt))*cnt; } } if(n>1){ cnt=1; cnt*=solve2(n); ans=ans/gcd(ans,cnt)*cnt; } return ans; } int main(){ LL t,n; init_prime(); matrix_a.x1=matrix_a.x2=matrix_a.x3=1; matrix_a.x4=0; matrix_b.x1=matrix_b.x4=1; matrix_b.x2=matrix_b.x3=0; dp[2]=3;dp[3]=8;dp[5]=20; scanf("%I64d",&t); while(t--){ scanf("%I64d",&n); printf("%I64d ",solve(n)); } return 0; }