bzoj3172: [Tjoi2013]单词

Description

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

Input

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

Output

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

Sample Input

3
a
aa
aaa

Sample Output

6
3
1
题解:
先将所有字符串拼起来,然后求一遍后缀数组,找到第以i个串开头的后缀的位置,求出向左走保证height[x]>=len[i]的最小的x,以及向右走height[y]>=len[i]的最大的y,那么答案就是y-x+2;
向左扩展或向右扩展在扩展的过程中,min(height[x])肯定单调不增,所以可以二分答案,rmq预处理区间最小值,时间复杂度O(nlogn)
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=1000000+210;
char A[maxn];
int sa[maxn],rk[maxn],tmp[maxn],c[maxn];
int n;
int minn[maxn][22];
int sum[maxn];
int len[maxn];
int height[maxn];
int lg[maxn];
int tot=0;
char B[maxn];
inline void get_sa(){
    int *rnk=rk,*tp=tmp;
    n=strlen(A+1);
    int m=300;
    for(int i=1;i<=n;i++)
        tp[i]=A[i],rnk[i]=A[i];
    for(int i=0;i<=m;i++)
        c[i]=0;
    for(int i=1;i<=n;i++)
        c[tp[i]]++;
    for(int i=1;i<=m;i++)
        c[i]+=c[i-1];
    for(int i=n;i>=1;i--)
        sa[c[tp[i]]--]=i;
    for(int j=1;j<=n;j<<=1){
        int p=0;
        for(int i=n-j+1;i<=n;i++)
            tp[++p]=i;
        for(int i=1;i<=n;i++)
            if(sa[i]>j)
                tp[++p]=sa[i]-j;
        for(int i=0;i<=m;i++)
            c[i]=0;
        for(int i=1;i<=n;i++)
            c[rnk[tp[i]]]++;
        for(int i=1;i<=m;i++)
            c[i]+=c[i-1];
        for(int i=n;i>=1;i--)
            sa[c[rnk[tp[i]]]--]=tp[i];
        swap(rnk,tp);
        rnk[sa[1]]=1;
        p=1;
        for(int i=2;i<=n;i++){
            int O1=sa[i]+j>n?-1:tp[sa[i]+j];
            int O2=sa[i-1]+j>n?-1:tp[sa[i-1]+j];
            rnk[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&O1==O2)?p:++p;
        }
        m=p;
        if(m>=n)
            break;
    }
}
inline void get_height(){
    for(int i=1;i<=tot;i++)
        rk[sa[i]]=i;
    int h=0;
    for(int i=1;i<=tot;i++){
        --h=h<0?0:h;
        int u=sa[rk[i]-1];
        while(A[u+h]==A[i+h])
            h++;
        height[rk[i]]=h;
    }
}
inline void rmq_pre(){
    for(int i=1;i<=tot;i++){
        int u=1,cnt=0;
        while(u<=i)
            u<<=1,cnt++;
        lg[i]=cnt-1;
    }
    for(int i=1;i<=tot;i++)
        minn[i][0]=height[i];
    for(int j=1;j<=30;j++)
        for(int i=1;i<=tot;i++){
            if(i+(1<<j)-1>tot)
                break;
            minn[i][j]=min(minn[i][j-1],minn[i+(1<<(j-1))][j-1]);
        }
}
inline int get_min(int l,int r){
    if(l>r)
        return 0x3f3f3f3f;
    int u=lg[r-l+1];
    return min(minn[l][u],minn[r-(1<<u)+1][u]);
}
inline int get_l(int x,int length){
    int l=1,r=x;
    while(l+1<r){
        int mid=(l+r)>>1;
        int u=get_min(mid+1,x);
        if(u>=length)
            r=mid;
        else l=mid;
    }
    if(get_min(l+1,x)>=length)
        return l;
    return r;
}
inline int get_r(int x,int length){
    int l=x,r=tot;
    while(l+1<r){
        int mid=(l+r)>>1;
        int u=get_min(x+1,mid);
        if(u>=length)
            l=mid;
        else r=mid;
    }
    if(get_min(x+1,r)>=length)
        return r;
    return l;
}
inline void work(int i){
    int t=rk[sum[i-1]+1];
    int l=get_l(t,len[i]),r=get_r(t,len[i]);
    //printf("%d %d\n",l,r);
    printf("%d\n",r-l+1);
}
int main(){
    //freopen("a.in","r",stdin);
    //freopen("a.out","w",stdout);
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%s",B+1);
        len[i]=strlen(B+1);
        for(int j=1;j<=len[i];j++)
            A[++tot]=B[j];
        if(i!=n)
            A[++tot]='z'+1;
        sum[i]=sum[i-1]+len[i]+(i!=n);
    }
    get_sa();
    get_height();
    rmq_pre();
    for(int i=1;i<=n;i++)
        work(i);
    //for(int i=1;i<=tot;i++)
    //  printf("%d ",sa[i]);
return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值