将每一个重置为0的点作为一段,那么它会导致后面为以x x为开头的斐波拿起数列的东西,那么设这一段是以x为开头,要快速转移到下一段,就可以解决这道题目
为了转移,我们要处理出下面的东西:1.求出x关于模k的逆元,也就是找到这个0原来的值,那么x*上一个数就是下一段的开头;2.通过这个值反推出这一段的长度(因为我们要求出第n个数),并通过矩阵乘法求出上一个值
当(x,k)不等于1,那么就没有逆元,也就是说不会出现特殊情况,直接矩乘即可
当(x,k)=1,通过exgcd求出逆元后,由于斐波那契数列关于模k的循环节不超过6k,预处理出每一个i满足$i\equiv f[j](mod\ k)$的最小的j即可反推出长度
当然由于n过于大,有可能要经过很多个这样的东西,但由于这样的值只有k个,因此最终会形成循环,我们只需要在循环中快速查找即可
主要思路就是这样,实现起来要注意细节(比如答案不是对k取模而是对k)和实现的方法(比如特殊的转移也可以用矩阵来转移)
1 #include<bits/stdc++.h>
2 using namespace std;
3 #define N 1000005
4 struct ji{
5 int a[3][3];
6 }s,zy1,zy2,ans;
7 int k,p,fi,ne,vis[N],las[N];
8 long long n,l[N];
9 ji cheng(ji a,ji b){
10 ji c;
11 memset(c.a,0,sizeof(c.a));
12 for(int i=0;i<3;i++)
13 for(int j=0;j<3;j++)
14 for(int k=0;k<3;k++)
15 c.a[i][j]=(c.a[i][j]+1LL*a.a[i][k]*b.a[k][j])%p;
16 return c;
17 }
18 void ksm(long long n){
19 if (!n)return;
20 ji s=zy1;
21 while (n){
22 if (n&1)ans=cheng(ans,s);
23 s=cheng(s,s);
24 n/=2;
25 }
26 }
27 int exgcd(int a,int b,int &x,int &y){
28 if (!b){
29 x=1;
30 y=0;
31 return a;
32 }
33 int t=exgcd(b,a%b,y,x);
34 y-=a/b*x;
35 return t;
36 }
37 int main(){
38 scanf("%lld%d%d",&n,&k,&p);
39 ans.a[0][0]=ans.a[1][1]=ans.a[2][2]=1;
40 s=ans;
41 zy1.a[0][1]=zy1.a[1][0]=zy1.a[1][1]=zy1.a[2][2]=1;
42 zy2=zy1;
43 zy2.a[2][0]=zy2.a[2][1]=p-1;
44 int a=1,b=1;
45 for(int i=3;;i++){
46 int c=(a+b)%k;
47 if (!vis[c]){
48 vis[c]=i;
49 las[c]=b;
50 }
51 if ((!c)&&(b==1))break;
52 a=b;
53 b=c;
54 }
55 fi=1;
56 int flag=0;
57 while (1){
58 if (exgcd(fi,k,a,b)>1)break;
59 a=(a%k+k)%k;
60 if (n<vis[a])break;
61 n-=vis[a];
62 ksm(vis[a]-1);
63 ans=cheng(ans,zy2);
64 ne=1LL*fi*las[a]%k;
65 if ((flag<2)&&(l[ne])){
66 flag++;
67 if (flag<2)swap(ans,s);
68 else{
69 swap(ans,s);
70 swap(s,zy1);
71 ksm(n/(l[ne]-n)+1);
72 n%=l[ne]-n;
73 swap(s,zy1);
74 }
75 memset(l,0,sizeof(l));
76 }
77 l[fi=ne]=n;
78 }
79 ksm(n);
80 printf("%d",(ans.a[1][0]+ans.a[2][0])%p);
81 }