[51Nod 1587] 半现串

Description

有两个串S,T。T的长度是d。我们说T在S中半现的条件是当T的某一个长度为 ⌊d/2⌋ 的子串是S的一个子串。(子串是在原串中连续出现的一段字符串)。
现在给定一个原串s,另外给出x,y,长度为d他们都只包含数字字符,问区间[x,y]中在s中半现的数字有多少个。
答案比较大,对 109+7 取余后输出。
1≤|s|≤1000,2≤d<=50

Solution

区间[x,y]的答案可以转化成[1,y]的减去[1,x-1]的

因为原串长度只有1000,那么考虑S所有长度为d/2的子串都扔到AC自动机里去

然后就可以按位DP了

设F[i][j][0,1]表示当前匹配到第i位,在AC自动机上走到j这个节点,当前是否顶住上限

当j这个节点所代表的字符串长度已经达到了d/2,那么计入答案,并将这个状态清空

小优化是当前状态如果是0就不转移

Code

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define N 50005
#define M 2005
#define mo 1000000007
#define LL long long
using namespace std;
char st[M],ls[51],rs[51];
int m,n,n1,t[N][10],fail[N],le[N],ed[N],d[N],m1;
LL f[51][N][2],cf[M],sq[51];
void make()
{
    d[1]=1;
    int l=0,r=1;
    while(l<r)
    {
        int k=d[++l];
        fo(i,0,9) 
        {
            if(t[k][i])
            {
                d[++r]=t[k][i];
                if(k==1) fail[t[k][i]]=k;
                else 
                {
                    int p=fail[k];
                    while(p>1&&!t[p][i]) p=fail[p];
                    if(t[p][i]) p=t[p][i];
                    fail[t[k][i]]=p;
                }
            }
        }
    }
}
LL ksm(LL k,LL n)
{
    LL s=1;
    for(;n;k=k*k%mo,n>>=1) if(n&1) s=s*k%mo;
    return s;
}
LL get(char *st,int m)
{
    LL ans=0;
    sq[m+1]=1;
    sq[m]=st[m]-'0'+1;
    fod(i,m-1,1) sq[i]=(sq[i+1]+((LL)(st[i]-'0'))*ksm(10,m-i)%mo)%mo;
    memset(f,0,sizeof(f));
    f[0][1][1]=1;
    fo(i,0,m)
    {
        fo(j,1,n1)
        {
            if(le[j]==m1/2) 
            {
                (ans+=f[i][j][0]*ksm(10,m-i)%mo)%=mo,f[i][j][0]=0;
                (ans+=f[i][j][1]*sq[i+1]%mo)%=mo,f[i][j][1]=0;
            }
            if(i<m&&f[i][j][0]+f[i][j][1]>0)
            fo(c,0,9)
            {
                int p=j;
                while(p>1&&!t[p][c]) p=fail[p];
                if(t[p][c]) p=t[p][c];
                (f[i+1][p][0]+=f[i][j][0])%=mo;
                if(c<=st[i+1]-'0')
                {
                    if(c==st[i+1]-'0') (f[i+1][p][1]+=f[i][j][1])%=mo;
                    else (f[i+1][p][0]+=f[i][j][1])%=mo;
                }
            }
        }
    }
    return ans;
}
int main()
{
    scanf("%s",st+1);
    n=strlen(st+1);
    scanf("\n%s",ls+1);
    scanf("\n%s",rs+1);
    m=strlen(ls+1);
    int j=m;
    m1=m;
    while(ls[j]=='0') ls[j]='9',j--;
    ls[j]=ls[j]-1;
    n1=1;
    fo(i,1,n-m1/2+1) 
    {
        int k=1;
        fo(j,1,m1/2)
        {
            int c=st[i+j-1]-'0';
            if(!t[k][c]) t[k][c]=++n1,le[n1]=j;
            k=t[k][c];
        }
        ed[k]=1;
    }
    make();
    printf("%lld\n",(get(rs,m1)-get(ls,m)+mo)%mo);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值