您的位置:首页 > 其它

bzoj 2432: [Noi2011]兔农 (数论+矩阵乘法)

2017-05-03 19:24 267 查看

题目描述

传送门

题目大意:f[1]=f[2]=1;i>2时,f[i]=f[i-1]+f[i-2], if(f[i] % k == 1) f


求f
%p。

题解

一道不错的矩阵乘法的题,感觉自己的数论知识完全跟不上啊。

斐波那契数列?一般都多少有一些规律或者循环节之类的吧。

我们以k=7为例,观察对7取模的Fibonacci数列

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位置为界,将数列分开,可以发现以下特点。

(1)每段开头必为相同两数,它恰是上一段的最末一位非0数;由于总共只有k−1种余数,所以不超过k段就会出现循环(如果有的话),比如上面k=7时的第2,3,4段就是循环节。

(2)设fibonacci数列的第i项为f[i],假如某段段首数字为x,那么这一段内第i个数即为x∗f[i],若某一段的长度为len,那么可以得到x∗f[len]≡1 mod k

如何求解?

(1)根据x∗f[len]≡1 mod k,求出f[len]

(2)根据f[len]反推出len

(3)由x*f[len-1]得到下一段的开头。

注意事项:

(1)根据x∗f[len]≡1 mod k,我们发现f[len]实际上就是x在mod k意义下的逆元。如果x,k不互质,那么就不存在逆元,也就不会有mod k=1的项,那么问题就转化成了最基本的用矩阵加速fibonacci的裸题。否则我们用扩欧求逆元,然后预处理出vis[i],表示f[len]=i的最小的len.这样我们就得到了某一段的长度。

还有一种情况需要考虑,就是不存在vis[i],这种情况与x,k不互质是一样的。

(2)结论:斐波那契数列模k后一定是0,1,1开头的纯循环,而且这个循环节的长度≤6k。有了这个结论我们就可以直接暴力预处理vis了

考虑如何用矩阵加速转移,只要不是每段的最后一个,那么转移都是相同的,可以用矩阵快速幂加速。

(ai−1ai1)*⎛⎝⎜010110001⎞⎠⎟=(aiai+11)

对于最后一位我们也可以用转移矩阵表示,每次做完快速幂直接乘上即可。

(ai−1ai1)*⎛⎝⎜10001−1001⎞⎠⎟=(ai−1ai−11)

基本思路已经说完了剩下的就是细节问题了。

(1)比如如果不是循环的就直接矩阵乘法优化。

(2)对于给定的n要先判断是在零散的段中,还是循环节中。如果是零散的段,不会太长,直接暴力合并每一段即可;如果是循环节那么我们将循环节中的段暴力合并然后整体再用快速幂加速,需要特别注意的是矩阵是不满足交换律的,所以乘的时候一定要注意顺序。

(3)因为有一个-1的关系,所以最后要注意(ans%p+p)%p

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define N 1000003
using namespace std;
LL n,m,p,mod,f[N*6],vis
,len
,inv
,pd
;
struct data{
LL t[4][4];
data(){
memset(t,0,sizeof(t));
}
}pr,cp,e,ans,ret
;
data operator*(data a,data b){
data c;
for (int i=1;i<=3;i++)
for (int j=1;j<=3;j++)
for (int k=1;k<=3;k++)
c.t[i][j]+=a.t[i][k]*b.t[k][j]%p,c.t[i][j]%=p;
return c;
}
data quickpow(data num,LL x)
{
data base=num; data ans;
for (int i=1;i<=3;i++) ans.t[i][i]=1;
while (x) {
if(x&1) ans=ans*base;
x>>=1;
base=base*base;
}
return ans;
}
void exgcd(LL a,LL b,LL &x,LL &y)
{
if (!b) {
x=1; y=0;
return;
}
exgcd(b,a%b,x,y);
LL t=y;
y=x-(a/b)*y;
x=t;
}
LL gcd(LL x,LL y)
{
LL r;
while (y) {
r=x%y;
x=y; y=r;
}
return x;
}
int main()
{
freopen("a.in","r",stdin);
scanf("%lld%lld%lld",&n,&m,&p);
f[1]=f[2]=1; bool flag=false;
for (int i=3;;i++){
f[i]=(f[i-1]+f[i-2])%m;
if (!vis[f[i]]) vis[f[i]]=i;
if (f[i]==1&&f[i-1]==1) break;
}
pr.t[1][2]=pr.t[2][1]=pr.t[2][2]=pr.t[3][3]=1;
for (int i=1;i<=3;i++) cp.t[i][i]=1; cp.t[3][2]=-1;
ans.t[1][1]=ans.t[1][3]=1;
for (LL t=1;n;) {
if (!inv[t]) {
if (gcd(t,m)!=1) inv[t]=-1;
else {
LL x,y;
exgcd(t,m,x,y);
inv[t]=(x+m)%m;
}
}
if (inv[t]==-1) {
ans=ans*quickpow(pr,n);
break;
}
if (!pd[t]||flag){
pd[t]=1;
if (!vis[inv[t]]) {
ans=ans*quickpow(pr,n);
break;
}
len[t]=vis[inv[t]];
if (n>=len[t]) {
n-=len[t];
ret[t]=quickpow(pr,len[t])*cp;
ans=ans*ret[t];
t*=f[len[t]-1]%m; t%=m;
} else ans=ans*quickpow(pr,n),n=0;
}
else {
data now; LL cnt=0;
for (int i=1;i<=3;i++) now.t[i][i]=1;
for (LL i=t*f[len[t]-1]%m;i!=t;(i*=f[len[i]-1])%=m)
now=now*ret[i],cnt+=len[i];
now=ret[t]*now; cnt+=len[t];
ans=ans*quickpow(now,n/cnt);
n%=cnt; flag=true;
}
}
printf("%lld\n",(ans.t[1][2]%p+p)%p);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  数论 矩阵乘法