【GDOI2016模拟3.9】暴走的图灵机

5 篇文章 0 订阅
4 篇文章 0 订阅

Description

现在你有两个字符串,l=’0’,r=’1’。每一次操作是把l=r,r=l’+r。l’表示操作前的l。求n次操作后,所得的l中含有多少个模式串S,个数%p。
n<=10^9,|S|<=10^5,p<=10^9

Solution

我们可以发现,这是个斐波那契数列类似的串,称作斐波那契串(Yves___大神命名,%%%)。
我们要求的就是这个串的第n项。
设Vs表示s这个串中所含模式串的个数,则Vs=Vl+Vr+C,其中C是跨越区间的数量。
那么我们可以暴力用kmp求出C。l串的后|S|-1位和r串的前|M|-1位加起来做就能保证一定跨区间了。
然后,可以发现当|l|>=2*m-2的时候,C就会出现长度为2的循环节。(自己想想为什么)
那么,我们可以暴力到出现两个长度>=2*m-2的串。然后用矩阵乘法优化。
至于转移矩阵,最好开4*4的。(3*3蒟蒻表示不会打)

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
#define M 1000005
#define N 35
using namespace std;
typedef ll arr[4][4];
char s[N][M],st[M],ch[M];
int next[M],c[N],n,m,p,a,tot;
ll ans[M],f[N];
void mult(arr a,arr b,arr d){
    arr c;memset(c,0,sizeof(c));
    fo(i,0,3)
        fo(j,0,3)
            fo(k,0,3) (c[i][j]+=a[i][k]*b[k][j]%p)%=p;
    memcpy(d,c,sizeof(c));
}
int kmp() {
    int ans=0,k=0;
    fo(i,1,tot) {
        while (k&&st[k+1]!=ch[i]) k=next[k];
        k+=(st[k+1]==ch[i]);if (k==m) ans++,k=next[k];
    }
    return ans;
}
int main() {
    scanf("%d%d%d",&n,&m,&p);
    scanf("%s",st+1);int k=0;
    fo(i,2,m) {
        while (k&&st[k+1]!=st[i]) k=next[k];
        next[i]=k+=(st[k+1]==st[i]);
    }
    s[0][1]='0';s[1][1]='1';f[0]=f[1]=1;
    if (m==1) {
        if (st[1]=='0') ans[0]=1;else ans[1]=1;
    }
    fo(i,2,n) {
        fo(j,1,f[i-2]) s[i][j]=s[i-2][j];
        fo(j,f[i-2]+1,f[i-1]+f[i-2]) s[i][j]=s[i-1][j-f[i-2]];
        f[i]=f[i-2]+f[i-1];tot=0;
        int x=min((ll)m-1,f[i-2]),y=min((ll)m-1,f[i-1]);
        fo(j,1,x) ch[++tot]=s[i-2][f[i-2]-x+j];
        fo(j,1,y) ch[++tot]=s[i-1][j];
        c[i]=kmp();if (f[i-1]>2*m) {a=i;break;}
    }
    fo(i,2,a) ans[i]=(ans[i-2]+ans[i-1]+c[i])%p;
    int y=(n-a)/2;
    if (!y) {
        if (n-a) printf("%lld",(ans[a-1]+ans[a]+c[a-1])%p);
        else printf("%lld",ans[a]);
        return 0;
    }
    arr g={1,1,0,0,1,2,0,0,1,1,1,0,0,1,0,1};
    arr t;memcpy(t,g,sizeof(t));
    for(y--;y>0;y/=2) {
        if (y%2) mult(t,g,t);
        mult(g,g,g);
    }
    f[0]=ans[a-1];f[1]=ans[a];f[2]=c[a-1];f[3]=c[a];
    memset(ans,0,sizeof(ans));
    fo(i,0,3) fo(j,0,3) ans[i]=(ans[i]+f[j]*t[j][i]%p)%p;
    if ((n-a)%2) printf("%lld",(ans[0]+ans[1]+ans[2])%p);
    else printf("%lld",ans[1]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值