后缀数组复习小记

定义:
suf[i]表示以i为开头的后缀
rank[i]表示suf[i]的排名,sa[i]表示排名为i的后缀
height[i]表示sa[i]和sa[i-1]的lcp
h[i]表示suf[i]和suf[sa[rank[i]-1]]的lcp
sa[rank[i]]=i,所以只要能求出rank,就可以求sa
倍增
求rank相当于把n个后缀排序,直接暴力排,显然是O(n^2*log(n))
我们可以考虑假如已经处理了每个位置开始的长度为k的字符串的排名,对于下一次排序,对于一个位置i我们可以以i的排名为第一关键字,以i+k的排名为第二关键字(i+k>n记做0),那么我们就可以处理出每个位置开始的长度为2k的字符串的排名。
也就是log(n)次即可,如果每次用快排那就是O(nlog^2n)
每次的排名肯定是<=n的,那我们用基数排序就可以O(n)排序。
对于两个关键字的基数排序,我们可以先处理出每个数的第二关键字的排名,然后对第一关键字排序时按第二关键字的排名插入即可。

如何处理height呢?
我们可以直接求h,因为height[rank[i]]=h[i]
有一个很好的性质h[i]>=h[i+1]-1,利用这个就可以做到O(n)处理height

简单应用
两个后缀suf[i]和suf[j]的lcp={height[rank[i]+1]~height[rank[j]]}的最小值

模板

void getsa(){
    int i,k=1,num,j,x,y;
    fo(i,1,n) d[s[i]]++;
    fo(i,1,255) d[i]+=d[i-1];
    fod(i,n,1) sa[d[s[i]]--]=i;
    num=0;
    fo(i,1,n){
        if (s[sa[i]]!=s[sa[i-1]]) num++;
        rank[sa[i]]=num;
    }
    for(;num<n;){
        num=0;
        fo(i,0,n) d[i]=0;
        fo(i,1,n) d[b[i]=rank[i+k]]++;
        fo(i,1,n) d[i]+=d[i-1];
        fod(i,n,1) c[d[b[i]]--]=i;
        fo(i,0,n) d[i]=0;
        fo(i,1,n) d[rank[i]]++;
        fo(i,1,n) d[i]+=d[i-1];
        fod(i,n,1) sa[d[rank[c[i]]]--]=c[i];
        fo(i,1,n){
            x=sa[i],y=sa[i-1];
            if ((rank[x]!=rank[y])||(rank[x]==rank[y]&&b[x]!=b[y])) num++;
            c[sa[i]]=num;
        }
        fo(i,1,n) rank[i]=c[i];
        k<<=1;
    }
    k=0;
    fo(i,1,n){
        if (k) k--;
        j=sa[rank[i]-1];
        for(;i+k<=n&&j+k<=n&&s[i+k]==s[j+k];k++);
        height[rank[i]]=k;
    }
    fo(i,1,n) w[i][0]=height[i];
    two[0]=1;
    fo(i,1,19)two[i]=two[i-1]<<1;
    j=0;
    fo(i,1,n){
        if (i==two[j+1]) j++;
        lo[i]=j;
    }
    fo(j,1,18) 
    fo(i,1,n-two[j]+1) w[i][j]=min(w[i][j-1],w[i+two[j-1]][j-1]);
}
int lcp(int x,int y){
    if (x==y) return n-x+1;
    x=rank[x],y=rank[y];
    if (x>y) swap(x,y);x++;
    int k=lo[y-x+1];
    return min(w[x][k],w[y-two[k]+1][k]);
}

你可能感兴趣的:(后缀数组)