【BZOJ 3172】 [Tjoi2013]单词

3172: [Tjoi2013]单词

Time Limit: 10 Sec Memory Limit: 512 MB
Submit: 1625 Solved: 749
[Submit][Status][Discuss]
Description

某人读论文,一篇论文是由许多单词组成。但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次。

Input

第一个一个整数N,表示有多少个单词,接下来N行每行一个单词。每个单词由小写字母组成,N<=200,单词长度不超过10^6

Output

输出N个整数,第i行的数字表示第i个单词在文章中出现了多少次。

Sample Input

3

a

aa

aaa
Sample Output

6

3

1

后缀数组/AC自动机fail树

①后缀数组的方法比较简单,只要把所有的字符串串在一起,两两之间用其他符号连接。

求出 height 数组后,只要暴力判断每一个串与 rank 在他附近的后缀的 height 值是否大于等于他的长度即可(因为两个后缀的最长公共前缀的长度是)。

②AC自动机需要建出fail树(即把fail指针反向得出的树),那么一个字符串出现的次数就是他的儿子的出现的次数之和。

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#define M 1001000
using namespace std;
int wv[M],c[M],x[M],y[M],r[M],wa[M],wb[M],sa[M],rk[M],he[M],pos[205],l[205];
char s[M];
int cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int *r,int sa[],int n,int m)
{
    int *x=wa,*y=wb,*t;
    for (int i=0;i<m;i++) c[i]=0;
    for (int i=0;i<n;i++) c[x[i]=r[i]]++;
    for (int i=1;i<m;i++) c[i]+=c[i-1];
    for (int i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
    int i,j,p;
    for (j=1,p=1;p<n;j*=2,m=p)
    {
        for (p=0,i=n-j;i<n;i++) y[p++]=i;
        for (i=0;i<n;i++) if (sa[i]>=j) y[p++]=sa[i]-j;
        for (i=0;i<n;i++) wv[i]=x[y[i]];
        for (i=0;i<m;i++) c[i]=0;
        for (i=0;i<n;i++) c[wv[i]]++;
        for (i=1;i<m;i++) c[i]+=c[i-1];
        for (i=n-1;i>=0;i--) sa[--c[wv[i]]]=y[i];
        for (t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
}
void Calheight(int *r,int *sa,int n)
{
    int i,j,k=0;
    for (i=1;i<=n;i++)
        rk[sa[i]]=i;
    for (i=0;i<n;i++)
    {
        if (k) k--;
        j=sa[rk[i]-1];
        while (r[j+k]==r[i+k])
            k++;
        he[rk[i]]=k;
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    int len=-1;
    for (int i=1;i<=n;i++)
    {
        scanf("%s",s);
        l[i]=strlen(s);
        pos[i]=len+1;
        for (int j=0;j<l[i];j++)
            r[++len]=s[j]-'a'+1;
        r[++len]=27;
    }
    r[++len]=0;
    da(r,sa,len+1,28);
    Calheight(r,sa,len);
    for (int i=1;i<=n;i++)
    {
        int j,x=rk[pos[i]],le,ri;
        for (j=x;j&&he[j]>=l[i];j--);
        le=j+1;
        for (j=x+1;j<=len&&he[j]>=l[i];j++);
        ri=j-1;
        printf("%d\n",ri-(le-1)+1);
    }
    return 0;
}

感悟:

WA是因为没有在字符串后面补0。

为什么要在字符串后面补0呢?
如果字符串后面两位都是a,那么当 j=1 的时候,求rank(即x数组)如果不补0就会认为a和aa是相同的,实际上a比aa小;
在j>1的排序中,根据cmp函数,如果a或者b>n-j,第一个相等条件就一定不满足,也就不存在第二个判断的时候越界的情况了。

综上,补0只在 j=1 的时候起到了作用。

你可能感兴趣的:(后缀数组,AC自动机,OI,bzoj)