codeforces 235C Cyclical Quest(后缀自动机)

题目链接:http://codeforces.com/contest/235/problem/C

题意:给出两个串S和T,问S有多少个长度为len(T)的子串s,使得s截成两半交换位置后得到的串是T?

思路:将S建立自动机,将两个T接在一起得到T'。在自动机上跑T'。自动机的每个节点记录这个串出现的次数。

 
  


const int KIND=26;
struct SAM
{
    SAM *son[KIND],*pre;
    int len,cnt,flag;
};

SAM sam[N],*head,*last,*b[N];
int d[N],cnt,f[N],len;
char s[N];

void initSAM()
{
    head=last=&sam[0];
    cnt=1;
}


void insert(int x)
{
    SAM *p=&sam[cnt++],*u=last;
    p->len=last->len+1;
    last=p;
    for(;u&&!u->son[x];u=u->pre) u->son[x]=p;
    if(!u) p->pre=head;
    else if(u->son[x]->len==u->len+1) p->pre=u->son[x];
    else
    {
        SAM *r=&sam[cnt++],*q=u->son[x];
        *r=*q; r->len=u->len+1;
        p->pre=q->pre=r;
        for(;u&&u->son[x]==q;u=u->pre) u->son[x]=r;
    }
}


int main()
{
    initSAM(); RD(s); len=strlen(s);
    int i,x;
    FOR0(i,len) insert(s[i]-'a');
    FOR0(i,cnt) d[sam[i].len]++;
    FOR1(i,len) d[i]+=d[i-1];
    FOR0(i,cnt) b[--d[sam[i].len]]=&sam[i];
    SAM *p=head;
    FOR0(i,len)
    {
        x=s[i]-'a';
        p=p->son[x];
        p->cnt=1;
    }
    FORL0(i,cnt-1)
    {
        p=b[i];
        if(p->pre) p->pre->cnt+=p->cnt;
    }
    int L,Q,ans,flag=0;
    RD(Q);
    while(Q--)
    {
        flag++;
        scanf("%s",s);len=strlen(s);
        p=head; L=0; ans=0;
        FOR0(i,len+len)
        {
            if(i>=len) x=s[i-len]-'a';
            else x=s[i]-'a';
            if(p->son[x]) L++,p=p->son[x];
            else
            {
                while(p&&!p->son[x]) p=p->pre;
                if(!p) L=0,p=head;
                else L=p->len+1,p=p->son[x];
            }
            if(L>=len)
            {
                while(p->pre&&p->pre->len>=len)
                //这个地方为什么要向前找呢
                //因为若p->pre->len>=len,则说明p所在节点向前len的串和
                //p->pre节点向前len的串是一样的
                //从自动机的性质上来看,p节点和p->pre节点是有p->pre->len的公共前缀的
                {
                    p=p->pre;
                    L=p->len;
                }
                if(flag!=p->flag) p->flag=flag,ans+=p->cnt;
            }
        }
        PR(ans);
    }
    return 0;
}
 
  

  

 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值