1. 程式人生 > >[BZOJ2432][Noi2011]兔農 矩陣乘法+exgcd

[BZOJ2432][Noi2011]兔農 矩陣乘法+exgcd

inline strong 一行 清晰 兩個 第一個 規律 bre 得到

2432: [Noi2011]兔農

Time Limit: 10 Sec Memory Limit: 256 MB

Description

農夫棟棟近年收入不景氣,正在他發愁如何能多賺點錢時,他聽到隔壁的小朋友在討論兔子繁殖的問題。
問題是這樣的:第一個月初有一對剛出生的小兔子,經過兩個月長大後,這對兔子從第三個月開始,每個月初生一對小兔子。新出生的小兔子生長兩個月後又能每個月生出一對小兔子。問第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 100

Sample Output

7

HINT

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)
不難發現,f[len]就是a在mod k意義下的逆元 所以,我們可以通過exgcd或者擴展歐拉定理,來快速求出f[len]。 如果這個逆元不存在,就證明後面的操作中沒有循環節了,我們直接矩陣快速冪遞推即可 由上面的性質1可以看出,下一行的第一個值b=a* f[len-1]%k 因此我們還需要知道len是多少 那麽我們再定義一個vis數組,其中vis[i]表示f值等於i的len最小值(有點拗口……) 那麽顯然,對於某一個f[len],vis[f[len]]=len(循環節中肯定是最短的啊……) 有了vis數組以後,我們就可以快速由f[len]求出len值了。 這樣,我們的處理流程就是:

(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