【GDOI模拟】暴走的图灵机

12 篇文章 0 订阅
7 篇文章 0 订阅

Description

你有l和r两个字符串,初始l=”0”,r=”1”,每轮操作定义如下。
将r变成原来的l+原来的r,这里的+表示将两个字符串连接起来
将l变成原来的l
给定一个长度为m的匹配串s,问操作n次后,l中包含了多少个s作为子串,答案对p取模。
n109,m104,0<p109

Solution

首先这道题目就是一个斐波拉契形式的字符串组合。
刚做这道题的时候,找到了一个字符串的规律,以为这是具有普遍性的,然后打了个矩阵……后来发现错了,最后还是打了个暴搜30分。
其实。当时可以进一步的思考。
首先我们可以发现这道题目的m并不是很大。
然后字符串加入的规则是上一个的r+l那么匹配串的个数也是r的答案加上l的答案,再加上因为拼接而产生的答案。然而我们只需要把r的后m-1个和l的前m-1个加起来,做一次KMP就好了。
再考虑m,我们发现当串的长度比2m-2大时头和尾基本就不变了,那么中间贡献的答案在模p意义下是有循环节的,可以发现这个循环节为2。
然后建立矩阵,矩阵快速幂就好了。

Code

#include<iostream>
#include<cstdio> 
#include<cstring>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
typedef long long ll;
using namespace std;
int i,j,k,l,t,n,m,p,r,next[30000],c[1000007],tot,ans1[1000007];
ll ans[1000007];
char s[40][1000007],st[1000007],sr[1000007];
struct node{
    ll ju[4][4];
    node friend operator *(node x,node y){
        int i,j,k;
        node z;
        memset(z.ju,0,sizeof(z.ju));
        fo(i,0,3){
            fo(j,0,3){
                fo(k,0,3){
                    z.ju[i][j]=(x.ju[i][k]*y.ju[k][j]%p+z.ju[i][j])%p;    
                }
            }
        }
        return z;
    }
}a,b;
node qsm(node x,int y){
    node z;
    memset(z.ju,0,sizeof(z.ju));
    int i;
    fo(i,0,3)z.ju[i][i]=1;
    while(y!=0){
        if(y&1!=0)z=z*x;
        x=x*x;
        y/=2;
    }
    return z;
}
int main(){
    scanf("%d%d%d",&n,&m,&p);
    scanf("%s",st+1);
    fo(i,2,m){
        while(j&&st[i]!=st[j+1])j=next[j];
        if(st[i]==st[j+1])j++;
        next[i]=j;
    }
    s[0][1]='0';s[1][1]='1';c[0]=c[1]=1;
    fo(i,2,n){
        fo(j,1,c[i-2])s[i][++c[i]]=s[i-2][j];
        fo(j,1,c[i-1])s[i][++c[i]]=s[i-1][j];
        tot=0;
        int u=min(m-1,c[i-2]),v=min(m-1,c[i-1]);
        fo(j,1,u)sr[++tot]=s[i-2][c[i-2]-u+j];
        fo(j,1,v)sr[++tot]=s[i-1][j];
        k=t=0;
        fo(j,1,tot){
            while(k&&sr[j]!=st[k+1])k=next[k];
            if(sr[j]==st[k+1])k++;
            if(k==m)ans1[i]=(ans1[i]+1)%p,k=next[k];
        }
        if(c[i-1]>2*m)break;
    }
    t=i;
    if(m==1){
        if(st[1]=='0')ans[0]=1;else ans[1]=1;
    }
    fo(i,2,t){
        ans[i]=(ans[i-2]+ans[i-1]+ans1[i])%p;
    }
    if(t==n){
        printf("%lld\n",ans[n]);
        return 0;
    }
    b.ju[0][0]=1;b.ju[0][1]=1;b.ju[0][2]=0;b.ju[0][3]=0;
    b.ju[1][0]=1;b.ju[1][1]=2;b.ju[1][2]=0;b.ju[1][3]=0;
    b.ju[2][0]=1;b.ju[2][1]=1;b.ju[2][2]=1;b.ju[2][3]=0;
    b.ju[3][0]=0;b.ju[3][1]=1;b.ju[3][2]=0;b.ju[3][3]=1;
    memset(a.ju,0,sizeof(a.ju));
    a.ju[0][0]=ans[t-1];a.ju[0][1]=ans[t];
    a.ju[0][2]=ans1[t-1];a.ju[0][3]=ans1[t];
    l=n-t;
    b=qsm(b,l/2);
    a=a*b;
    if(l%2){
        printf("%lld\n",(a.ju[0][0]+a.ju[0][1]+a.ju[0][2])%p);
    }
    else{
        printf("%lld\n",a.ju[0][1]);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值