后缀数组模板

const int N=2e4+10;
int n,sa[N],rnk[N],height[N];
char s[N];

//sa数组表示字典序为i的后缀是谁,rnk数组表示某个后缀的排名
void buildSA(int m=128){
    int cnt[N],rnk1[N],rnk2[N],tmpSA[N];    //cnt[i]用来记录rnk小于等于i的子串已经有多少个了,这样可以直接用cnt[rnk[i]]更新sa
    //n=strlen(s)+1;
    //s[n]=0;    存疑
    fill(cnt,cnt+m,0);
    for(int i=0;i<n;++i)
        cnt[(int)s[i]]++;
    for(int i=1;i<m;i++)
        cnt[i]+=cnt[i-1];
    for(int i=0;i<n;i++)
        rnk[i]=cnt[(int)s[i]]-1;

    for(int l=1;l<n;l<<=1){
        for(int i=0;i<n;++i){
            rnk1[i]=rnk[i];
            rnk2[i]=(i+l<n?rnk[i+l]:0);
        }
        memset(cnt,0,sizeof(cnt));
        for(int i=0;i<n;++i)
            cnt[rnk2[i]]++;
        for(int i=1;i<n;++i)
            cnt[i]+=cnt[i-1];
        for(int i=n-1;i>=0;--i)
            tmpSA[--cnt[rnk2[i]]]=i;    //--是为了区分rnk相同的串,后访问的排序靠前一些
        memset(cnt,0,sizeof(cnt));
        for(int i=0;i<n;++i)
            cnt[rnk1[i]]++;
        for(int i=1;i<n;++i)
            cnt[i]+=cnt[i-1];
        for(int i=n-1;i>=0;--i)
            sa[--cnt[rnk1[tmpSA[i]]]]=tmpSA[i];    //是按照tmpSA的逆序计算,也就是rnk2较大的对应的cnt值会大一些,排在rnk1相同而rnk2较小的后面
        //cnt代表每个桶的大小
        bool uniq=true;
        rnk[sa[0]]=0;       //构建新的rank数组
        for(int i=1;i<n;++i){
            rnk[sa[i]]=rnk[sa[i-1]];
            if(rnk1[sa[i]]==rnk1[sa[i-1]]&&rnk2[sa[i]]==rnk2[sa[i-1]])
                uniq=false;     //这里不能break,因为还要继续把当前rank数组算完
            else
                rnk[sa[i]]++;
        }
        if(uniq)
            break;
    }
}
//height[i]表示位于sa[i-1]和sa[i]的两个后缀的最长前缀
void getHeight() {
    int k = 0;
    for (int i = 0; i < n - 1; ++i) {
        if (k) --k;        //新的长度不会比k-1小,是结论
        int j = sa[rnk[i] - 1];
        while (s[i + k] == s[j + k]) k++;
        height[rnk[i]] = k;
    }
}

//满足lcp(l,r)=min{height[i+1],...height[r]},故是RMQ问题

void initST(){    //用ST表进行RMQ
    for(int i=0;i<n;i++)
        ST[i][0]=height[i];    //实际应该是ST[i][1],但这么做方便后面合并
    for(int j=1;(1<<j)<=n;j++){
        for(int i=0;i+(1<<j)-1<n;i++)
            ST[i][j]=min(ST[i][j-1],ST[i+(1<<(j-1))][j-1]);
    }
}

//lcp:最长公共前缀,一般指两个后缀的最长公共前缀
int lcp(int l,int r){
    if(l==r)
        return n-sa[l];
    if(l>r)
        swap(l,r);
    l++;    //因为现在ST表中存的都比下标早一个
    int k=0;
    while(1<<(k+1)<=r-l+1) ++k;
    return min(ST[l][k],ST[r-(1<<k)+1][k]);
}

 

一道板子

题目很妙,难点在于想到用后缀数组来解决问题。

全部评论

相关推荐

不愿透露姓名的神秘牛友
今天 10:18
点赞 评论 收藏
分享
offer多多的六边形战士很无语:看了你的博客,感觉挺不错的,可以把你的访问量和粉丝数在简历里提一下,闪光点(仅个人意见)
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务