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

27 篇文章 0 订阅

题目描述

传送门

题目大意:f[1]=f[2]=1;i>2时,f[i]=f[i-1]+f[i-2], if(f[i] % k == 1) f[n]–
求f[n]%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个数即为 xf[i] ,若某一段的长度为len,那么可以得到 xf[len]1 mod k

如何求解?
(1)根据 xf[len]1 mod k ,求出 f[len]
(2)根据 f[len] 反推出len
(3)由x*f[len-1]得到下一段的开头。

注意事项:
(1)根据 xf[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

考虑如何用矩阵加速转移,只要不是每段的最后一个,那么转移都是相同的,可以用矩阵快速幂加速。
(ai1ai1) * 010110001 = (aiai+11)
对于最后一位我们也可以用转移矩阵表示,每次做完快速幂直接乘上即可。
(ai1ai1) * 100011001 = (ai1ai11)

基本思路已经说完了剩下的就是细节问题了。
(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[N],len[N],inv[N],pd[N];
struct data{
    LL t[4][4];
    data(){
        memset(t,0,sizeof(t));
    }
}pr,cp,e,ans,ret[N];
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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值