[BZOJ2432][Noi2011]兔農 矩陣乘法+exgcd
2432: [Noi2011]兔農
Time Limit: 10 Sec Memory Limit: 256 MBDescription
農夫棟棟近年收入不景氣,正在他發愁如何能多賺點錢時,他聽到隔壁的小朋友在討論兔子繁殖的問題。
問題是這樣的:第一個月初有一對剛出生的小兔子,經過兩個月長大後,這對兔子從第三個月開始,每個月初生一對小兔子。新出生的小兔子生長兩個月後又能每個月生出一對小兔子。問第n個月有多少只兔子?
聰明的你可能已經發現,第n個月的兔子數正好是第n個Fibonacci(斐波那契)數。棟棟不懂什麽是Fibonacci數,但他也發現了規律:第i+2個月的兔子數等於第i個月的兔子數加上第i+1個月的兔子數。前幾個月的兔子數依次為:
1 1 2 3 5 8 13 21 34 …
棟棟發現越到後面兔子數增長的越快,期待養兔子一定能賺大錢,於是棟棟在第一個月初買了一對小兔子開始飼養。
每天,棟棟都要給兔子們餵食,兔子們吃食時非常特別,總是每k對兔子圍成一圈,最後剩下的不足k對的圍成一圈,由於兔子特別害怕孤獨,從第三個月開始,如果吃食時圍成某一個圈的只有一對兔子,這對兔子就會很快死掉。
我們假設死去的總是剛出生的兔子,那麽每個月的兔子數仍然是可以計算的。例如,當k=7時,前幾個月的兔子數依次為:
1 1 2 3 5 7 12 19 31 49 80 …
給定n,你能幫助棟棟計算第n個月他有多少對兔子麽?由於答案可能非常大,你只需要告訴棟棟第n個月的兔子對數除p的余數即可。
Input
輸入一行,包含三個正整數n, k, p。
Output
輸出一行,包含一個整數,表示棟棟第n個月的兔子對數除p的余數。
Sample Input
6 7 100Sample Output
7HINT
1<=N<=10^18
2<=K<=10^6
2<=P<=10^9 題解: 這題怎麽說呢……又愛又恨…… 暴力分給的特別足,有75分,作為noi題目簡直太良心了! 可是,如果想拿到剩下的25分特別困難……思路必須十分清晰 在5分鐘打完暴力分之後,我們不妨打個表觀察一下。 拿樣例的%7作為例子 按照題意計算的數列,%7後大概長這樣(為了美觀,我們每出現一個0換一次行):1, 1, 2, 3, 5, 0,
5, 5, 3, 0,
3, 3, 6, 2, 0,
2, 2, 4, 6, 3, 2, 5, 0,
5, 5, 3, 0,
3, 3, 6, 2, 0,
性質1:我們發現,每段開頭必為相同的兩數,並且它們恰是上一段的最末一位非0數;由於總共只有k?1種余數,
所以不超過k段就會出現循環(如果有的話),比如上面k=7時的上面的數列,
5, 5, 3, 0,3, 3, 6, 2, 0,
2, 2, 4, 6, 3, 2, 5, 0, 就是一個循環節。 然後我們可以發現一些新的性質: 性質2:設開頭的兩個數為a,那麽由於這是斐波那契數列,後面的數會變成2*a%k,3*a%k,5*a%k…… 這其實很容易證明。如果上一行出現了“……a 0 ”,那麽這一行第一位是a+0=a,第二位是0+a=a,後面一樣遞推 第i項的系數,就是斐波那契數列第i項的值。 那麽,在我們減1使最後一個余數變為0之前,應該有: a* f[len] ≡ 1 (mod k)
(1)根據a* f[len] ≡ 1 (mod k),求出f[len]
(2)根據f[len]和vis數組反推出len
(3)由x*f[len-1]得到下一段的開頭。
現在我們的問題就變成了如何求vis數組?
然後據說有一個很強的結論:斐波那契數列在模k意義下一定是以0 1 1……為開頭的循環,並且循環節長度<=6*k(但也可能很長,比n大),因此不用擔心算不完,只需要暴力遞推記錄每一位%k的f值和vis值即可
還剩下一點就是轉移矩陣:在行內使用下面的A矩陣,在行間轉移使用B矩陣。答案矩陣一開始長C這樣
1 1 0 1 0 0 0 1 1
1 0 0 0 1 0
0 0 1 -1 0 1
A B C
這樣,我們的處理過程就是:遞推記錄vis數組和f數組->建立轉移矩陣->計算每一行的轉移矩陣->如果出現循環節,用矩陣乘法快速處理->將剩余的轉移用A矩陣乘到C中
這樣,本題就被我們切掉了……真的是很不錯的一道題。具體實現時還有一些小點需要註意。代碼見下:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <cstdlib> 5 #include <ctime> 6 using namespace std; 7 typedef long long LL; 8 const int N=1000010; 9 LL n,k,p,vis[N],f[N*6],ni[N],len[N]; 10 bool ext[N]; 11 void exgcd(LL a,LL b,LL&x,LL &y,LL &d) 12 { 13 if(b==0)d=a,x=1,y=0; 14 else exgcd(b,a%b,y,x,d),y-=x*(a/b); 15 } 16 inline LL inv(LL a,LL mod) 17 { 18 LL d,x,y;exgcd(a,mod,x,y,d); 19 return d==1?(x+mod)%mod:-1; 20 } 21 struct marx 22 { 23 LL m[4][4]; 24 marx(){} 25 inline LL *operator [](LL x){return m[x];} 26 inline void clear(){memset(m,0,sizeof(m));} 27 marx operator * (const marx &b) const 28 { 29 marx c;c.clear(); 30 for(int k=1;k<=3;k++) 31 for(int i=1;i<=3;i++) 32 for(int j=1;j<=3;j++) 33 c.m[i][j]=(c.m[i][j]%p+m[i][k]%p*b.m[k][j]%p)%p; 34 return c; 35 } 36 }mat[N],A,B,C,ans; 37 inline marx poww(marx x,LL len) 38 { 39 marx ret;ret.clear(); 40 ret[1][1]=ret[2][2]=ret[3][3]=1; 41 while(len) 42 { 43 if(len&1)ret=ret*x; 44 len>>=1;x=x*x; 45 } 46 return ret; 47 } 48 int main() 49 { 50 scanf("%lld%lld%lld",&n,&k,&p); 51 f[1]=f[2]=1; 52 for(int i=3;;i++) 53 { 54 f[i]=(f[i-1]+f[i-2])%k; 55 if(!vis[f[i]])vis[f[i]]=i; 56 if(f[i]==f[i-1]&&f[i]==1)break; 57 } 58 A[1][1]=A[1][2]=A[2][1]=A[3][3]=1; 59 B[1][1]=B[2][2]=B[3][3]=1,B[3][1]=-1; 60 ans[1][2]=ans[1][3]=1; 61 LL x=1;bool flag=0; 62 while(n) 63 { 64 if(!ni[x])ni[x]=inv(x,k); 65 if(ni[x]==-1)ans=ans*poww(A,n),n=0; 66 else 67 { 68 if(!ext[x]||flag) 69 { 70 ext[x]=1; 71 if(!vis[ni[x]]) 72 ans=ans*poww(A,n),n=0; 73 else 74 { 75 len[x]=vis[ni[x]]; 76 if(n>=len[x]) 77 { 78 n-=len[x]; 79 mat[x]=poww(A,len[x])*B; 80 ans=ans*mat[x],x=x*f[len[x]-1]%k; 81 } 82 else ans=ans*poww(A,n),n=0; 83 } 84 } 85 else 86 { 87 LL cnt=0; 88 C.clear();C[1][1]=C[2][2]=C[3][3]=1; 89 for(LL i=x*f[len[x]-1]%k;i!=x;i=i*f[len[i]-1]%k) 90 cnt+=len[i],C=C*mat[i]; 91 cnt+=len[x],C=mat[x]*C; 92 ans=ans*poww(C,n/cnt); 93 n%=cnt,flag=1; 94 } 95 } 96 } 97 printf("%lld",(ans[1][1]%p+p)%p); 98 }
[BZOJ2432][Noi2011]兔農 矩陣乘法+exgcd