HDU 3518 Boring counting(后缀自动机 SAM)

题意:求一个字符串中重复出现两次以上且不重叠的不同字串的个数。比如aaaa中 a和aa符合要求,而aaa和aaaa不符合要求。

思路:好像标程给的是后缀数组,但我是后缀自动机过的,我们只要在后缀自动机中的每一个状态里,设l,r分别表示在这个状态中的子串集合的最左与最右位置,num表示这个状态中的子串一共出现了多少次。

因为在后缀自动机中,每一个状态代表了一类子串,(设一个状态为p)它们的长度范围是[p->par->val+1,p->val],(不妨设为left和right)则如果p->r-p->l>=p->val.则说明这个状态中的每一个字串都符合要求,所以答案要加上right-left+1,如果p->r-p->l处在left和right之间,则说明只有一部分的子串(也就是长度不大于p->r-p->l的子串)符合要求,答案要加上p->r-p->l-left+1,至于left,right和num的更新,由SAM的性质,我们可以先将SAM拓扑排序,然后自底向上更新即可。具体代码如下:

#include <iostream>
#include <string.h>
#include <stdio.h>
#define maxn 2010
#define Smaxn 26
#define mod 2012
#define inf 21000000
using namespace std;
struct node
{
    node *par,*go[Smaxn];
    int left,right;
    int num;
    int po;
    int val;
}*root,*tail,que[maxn],*top[maxn];
int tot;
char str[maxn>>1];
void add(int c,int l,int po)
{
    node *p=tail,*np=&que[tot++];
    np->val=l;
    np->po=po;
    while(p&&p->go[c]==NULL)
    p->go[c]=np,p=p->par;
    if(p==NULL) np->par=root;
    else
    {
        node *q=p->go[c];
        if(p->val+1==q->val) np->par=q;
        else
        {
            node *nq=&que[tot++];
            *nq=*q;
            nq->val=p->val+1;
            np->par=q->par=nq;
            while(p&&p->go[c]==q) p->go[c]=nq,p=p->par;
        }
    }
    tail=np;
}
int c[maxn],len;
void init()
{
    memset(que,0,sizeof(que));
    tot=0;
    len=1;
    root=tail=&que[tot++];
}
void solve()
{
    memset(c,0,sizeof(c));
    int i;
    for(i=0;i<tot;i++)
    c[que[i].val]++;
    for(i=1;i<len;i++)
    c[i]+=c[i-1];
    for(i=0;i<tot;i++)
    top[--c[que[i].val]]=&que[i];
    for(node *p=root;;p=p->go[str[p->val+1]-'a'])
    {
        p->num=1;
        p->left=p->right=p->po;
        if (p->val==len-1)break;
    }
    for(i=tot-1;i>=0;i--)
    {
        node *p=top[i];
        if(p->left==0&&p->right==0)
        {
            p->left=p->right=p->po;
        }
        if(p->par)
        {
            node *q=p->par;
            q->num+=p->num;
            if(q->left==0||q->left>p->left)
            q->left=p->left;
            if(q->right==0||q->right<p->right)
            q->right=p->right;
        }
    }
    int ans=0;
    for(i=1;i<tot;i++)
    {
        if(que[i].num>1)
        {
            int ma=que[i].val,mi=que[i].par->val+1,le=que[i].right-que[i].left;
            if(le>=ma)
            ans+=(ma-mi+1);
            else if(le<ma&&le>=mi)
            {
                ans+=(le-mi+1);
            }
        }
    }
    printf("%d\n",ans);
}
int main()
{
    while(1)
    {
        scanf("%s",str+1);
        int i,l=strlen(str+1);
        if(str[1]=='#')
        break;
        init();
        for(i=1;i<=l;i++)
        {
            add(str[i]-'a',len++,i);
        }
        solve();
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值