首页 > 代码库 > hdu 4622 Reincarnation(后缀数组|后缀自动机|KMP)

hdu 4622 Reincarnation(后缀数组|后缀自动机|KMP)

Reincarnation

Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 131072/65536 K (Java/Others)
Total Submission(s): 2138    Accepted Submission(s): 732


Problem Description
Now you are back,and have a task to do:
Given you a string s consist of lower-case English letters only,denote f(s) as the number of distinct sub-string of s.
And you have some query,each time you should calculate f(s[l...r]), s[l...r] means the sub-string of s start from l end at r.
 

Input
The first line contains integer T(1<=T<=5), denote the number of the test cases.
For each test cases,the first line contains a string s(1 <= length of s <= 2000).
Denote the length of s by n.
The second line contains an integer Q(1 <= Q <= 10000),denote the number of queries.
Then Q lines follows,each lines contains two integer l, r(1 <= l <= r <= n), denote a query.
 

Output
For each test cases,for each query,print the answer in one line.
 

Sample Input
2 bbaba 5 3 4 2 2 2 5 2 4 1 4 baaba 5 3 3 3 4 1 4 3 5 5 5
 

Sample Output
3 1 7 5 8 1 3 8 5 1
Hint
I won‘t do anything against hash because I am nice.Of course this problem has a solution that don‘t rely on hash.
 

Source
2013 Multi-University Training Contest 3
 

Recommend
zhuyuanchen520   |   We have carefully selected several similar problems for you:  5065 5064 5062 5061 5059 
 
题意:
给你一个长度不超过2000只由小写字母组成的字符串。然后q个询问q不超过10000.每个询问询问一个区间[l,r]内有多少不同的子串。
思路:
如果是统计整个串有多少个不同的子串这题就是模板题了。就按照论文上的方法搞就行了。关键是要求一个区间有多少不同的子串而不是整个串。但是我们不能对每个询问都跑一次后缀数组吧。这样q*n*log(n)肯定T了。但这题用后缀数组还是可做的。但是要对后缀数组有比较深的理解。论文里统计不同子串的做法利用的就是后缀的前缀就是子串。那么对于每一个后缀我们可以看它能贡献多少不同的前缀就是答案了。而这题我们队整个串跑一边sa。得到整个串的height数组。可不可以类似处理出来呢。我开始的做法是根据l<=sa[i]<=r来确定这是目标串的后缀。然后只对这个范围内的串类似论文方式处理。但这样做是错误的。因为你得到的是整个串的sa。
可能存在这样一种情况。
比如
串为abac
而你询问为[1,3]
这个区间的sa情况为
3 a
1 aba
2 ba
但是整个串的sa为
1 abac
3 ac
2 bac
4 c
这样3就排到1的后面去了。
所以我们不能这么做。我们必须理解统计不同子串的精髓。统计不同后缀贡献的的前缀。要做到不重不漏。
所以当[l,r]范围内的一个串i加进去对答案的贡献必须减掉它和之前所有串重复的部分。也就是和它lcp最大的那个。就行了即为ml。答案就为r-sa[i]+1-min(r-sa[i]+1,ml)。
详细见代码:
#include<algorithm>
#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=2010;
typedef long long ll;
char txt[maxn];
int sa[maxn],T1[maxn],T2[maxn],ct[maxn],he[maxn],rk[maxn],n,m;
void getsa(char *st)
{
    int i,k,p,*x=T1,*y=T2;
    for(i=0; i<m; i++) ct[i]=0;
    for(i=0; i<n; i++) ct[x[i]=st[i]]++;
    for(i=1; i<m; i++) ct[i]+=ct[i-1];
    for(i=n-1; i>=0; i--)
        sa[--ct[x[i]]]=i;
    for(k=1,p=1; p<n; k<<=1,m=p)
    {
        for(p=0,i=n-k; i<n; i++) y[p++]=i;
        for(i=0; i<n; i++) if(sa[i]>=k) y[p++]=sa[i]-k;
        for(i=0; i<m; i++) ct[i]=0;
        for(i=0; i<n; i++) ct[x[y[i]]]++;
        for(i=1; i<m; i++) ct[i]+=ct[i-1];
        for(i=n-1; i>=0; i--) sa[--ct[x[y[i]]]]=y[i];
        for(swap(x,y),p=1,x[sa[0]]=0,i=1; i<n; i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
    }
}
void gethe(char *st)
{
    int i,j,k=0;
    for(i=0;i<n;i++) rk[sa[i]]=i;
    for(i=0;i<n-1;i++)
    {
        if(k) k--;
        j=sa[rk[i]-1];
        while(st[i+k]==st[j+k]) k++;
        he[rk[i]]=k;
    }
}
int main()
{
    int t,i,q,le,ri,lcp,ml,ans,tp;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%s",txt);
        n=strlen(txt)+1;
        m=150;
        getsa(txt);
        gethe(txt);
        scanf("%d",&q);
        while(q--)
        {
            scanf("%d%d",&le,&ri);
            le--,ri--;
            ans=lcp=ml=0;
            for(i=1;i<n;i++)
            {
                lcp=min(lcp,he[i]);
                ml=min(ml,lcp);
                if(sa[i]>=le&&sa[i]<=ri)
                {
                    ans+=ri-sa[i]+1-min(ri-sa[i]+1,ml);
                    lcp=he[i+1];
                    tp=min(lcp,ri-sa[i]+1);
                    ml=max(ml,tp);
                }
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}
/*
1
lelwprbultllgkhhhyjchhngo
1
15 21
*/
第二种方法是后缀自动机。这个方法就比较简单了(不代表后缀自动机简单)。后缀自动机花了很长时间才懂。主要原因感觉网上这方面通俗的资料比较少。也可能是自己太弱了吧。后缀自动机是我学的感觉学起来最难的算法没有之一。打算打完现场赛自己写个简单的学习笔记来拯救像我这样苦逼的少年。这题只需在后缀自动机结点里多维护个信息记录到该节点有多少不同子串就行了。每次就可以知道增加了多少子串。建n次后缀自动机预处理出所有答案就好啦。
详细见代码:
#include<algorithm>
#include<iostream>
#include<string.h>
#include<stdio.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=2010<<1;
typedef long long ll;
struct node
{
    node *f,*ch[30];
    int len,way;
    void init()
    {
        way=0;
        memset(ch,0,sizeof ch);
    }
}*root,*tail,sam[maxn<<1];
int tot,dp[2010][2010];
char txt[maxn];
void init()
{
    tot=0;
    root=tail=&sam[tot++];
    root->f=NULL;
    root->len=0;
    root->init();
    root->way=1;
}
void Insert(int c)
{
    node *p=tail,*np=&sam[tot++];
    np->init(),np->len=p->len+1;
    while(p&&!p->ch[c])
    {
        np->way+=p->way;
        p->ch[c]=np,p=p->f;
    }
    tail=np;
    if(!p)
        np->f=root;
    else
    {
        if(p->ch[c]->len==p->len+1)
            np->f=p->ch[c];
        else
        {
            node *q=p->ch[c],*nq=&sam[tot++];
            *nq=*q;
            nq->len=p->len+1;
            nq->way=0;
            q->f=np->f=nq;
            while(p&&p->ch[c]==q)
            {
                p->ch[c]->way-=p->way;
                nq->way+=p->way;
                p->ch[c]=nq,p=p->f;
            }
        }
    }
}
int main()
{
    int i,j,t,len,q,le,ri;

    scanf("%d",&t);
    while(t--)
    {
        scanf("%s",txt);
        len=strlen(txt);
        for(i=0;i<len;i++)
        {
            init();
            for(j=i;j<len;j++)
            {
                Insert(txt[j]-'a');
                dp[i][j]=tail->way;
            }
            for(j=i+1;j<len;j++)
                dp[i][j]+=dp[i][j-1];
        }
        scanf("%d",&q);
        while(q--)
        {
            scanf("%d%d",&le,&ri);
            printf("%d\n",dp[le-1][ri-1]);
        }
    }
    return 0;
}

第三种事kmp的做法。由于还没写代码。先占个坑吧。

hdu 4622 Reincarnation(后缀数组|后缀自动机|KMP)