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

37 篇文章 0 订阅
21 篇文章 0 订阅

题目描述

l=’0’,r=’1’。每次操作把r变成l+r,把l变成r。求n次操作后,l中子串T的数量mod P

输入

第一行n,m,t,p。m为T的长度。第二行为T

输出

答案mod p

样例输入

7 3 100
101

样例输出

8

数据范围

N<= 109 ,M<=10000,
对于30%,N<= 105

题解

N巨大,显然找规律。
设当前l串中T的个数为a1,r串中为a2,则l+r之后答案为a1+a2+C。C为合并之后的T的个数(T必须穿过合并的那个位置)。或者说C为合并之后中间部分多出来的字串数量。
C只能是l的后M-1和r的前M-1合并后子串的数量。如果任意一个字串超过或等于M,就不能保证C为合并后多出来的数量。
列个表
l r
0 1
1 01
01 101
101 01101
暴力一下l和r,发现当两个串长度都超过M之后,合并后中间两个数为10,11,10,11……,这时C就进入了长度为2的循环。
你会说这里只算了中间两个数,你可以试一试两边都取长一些,会发现C仍然为长度为2的循环。
记为C1,C2。
这下就好办了。
已知a1,a2,C1,C2,之后,可以推出a3=a1+a2+C1,a4=a2+a3+C2,a5=a3+a4+C1……
那么怎么求a1,a2,C1,C2呢?先知道求一个串中指定字串的数量用KMP。具体请看KMP算法详解
发现M只有10000。那么暴力l和r直到两个串长度都>=M,a1就是当前l中T的个数,a2就是r中T的个数。C1就是l的后M-1位与r的前M-1位合并后得到的串中T的数量。C2就是l的前M-1位与r的后M-1位合并后得到的串中T的数量。接着递推就可以60分了。
还有四十分连O(n)都会超时,那么就用矩阵乘法就行了。

代码

type
    arr=array[1..4,1..4] of int64;
    arr2=array[1..4] of int64;
var
    n,m,x,y,i,j,v1,v2,mo,ans:longint;
    s0,s1,ss,s2,s3:ansistring;
    next:array[0..100000] of longint;
    bz:boolean;
    a:arr=((1,1,0,0),
           (1,2,0,0),
           (1,1,1,0),
           (0,1,0,1));
    f:arr2;
function kmp(s1,s2:ansistring):longint;
var
    i,j,k,n,ans:longint;
begin
    j:=0;n:=length(s1);ans:=0;
    for i:=1 to n do
    begin
        while (j>0)and(s1[i]<>s2[j+1]) do begin j:=next[j];end;
        if s1[i]=s2[j+1] then inc(j);
        if j=m then
        begin
            inc(ans);
            j:=next[j];
        end;
    end;
    exit(ans);
end;
function matrix(a,b:arr):arr;
var
    i,j,k:longint;
begin
    fillchar(matrix,sizeof(matrix),0);
    for i:=1 to 4 do
        for j:=1 to 4 do
            for k:=1 to 4 do
                matrix[i,j]:=(matrix[i,j]+a[i,k]*b[k,j]+mo)mod mo;
end;
function matrix2(a:arr2;b:arr):arr2;
var
    i,j,k:longint;
begin
    fillchar(matrix2,sizeof(matrix2),0);
    for j:=1 to 4 do
        for k:=1 to 4 do
            matrix2[j]:=(matrix2[j]+a[k]*b[k,j]+mo)mod mo;
end;
function mi(a:arr;b:int64):arr;
var
    c:arr;
begin
    if b=1 then exit(a);
    if b mod 2=0 then
    begin
        c:=mi(a,b div 2);
        exit(matrix(c,c));
    end
    else
    begin
        c:=mi(a,b div 2);
        exit(matrix(matrix(c,c),a));
    end;
end;
begin
    assign(input,'4374.in'); reset(input);
    //assign(output,'4374.out'); rewrite(output);
    readln(n,m,mo);
    readln(ss);
    j:=0;
    if n=1 then begin writeln(0);exit;end;
    for i:=2 to m do
    begin
        while (j>0)and(ss[i]<>ss[j+1]) do j:=next[j];
        if ss[i]=ss[j+1] then inc(j);
        next[i]:=j;
    end;
    //-----------
    s0:='0';s1:='1';
    while n>0 do
    begin
        s2:=s1;s1:=s0+s1;s0:=s2;
        if (length(s0)>=m)and(length(s1)>=m)then break;
        dec(n);
    end;
    f[1]:=kmp(s0,ss);f[2]:=kmp(s1,ss);
    s2:=s1;s3:=s0;s1:=copy(s1,1,m-1);delete(s0,1,length(s0)-m+1);
    v1:=kmp(s0+s1,ss);
    s1:=s3+s2;s0:=s2;s1:=copy(s1,1,m-1);delete(s0,1,length(s0)-m+1);
    v2:=kmp(s0+s1,ss);
    f[3]:=v1;f[4]:=v2;
    a:=mi(a,(n-1) div 2);
    f:=matrix2(f,a);
    n:=n mod 2;if n=0 then n:=2;
    writeln(f[n]mod mo);
    close(input);close(output);
end.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值