后缀数组复习小记

后缀数组复习小记Suffix Array

后缀数组,顾名思义就是处理后缀的数组。例如daabbc的后缀:daabbc,aabbc,abbc,bbc,bc,c六个后缀。
——>定义sa[i]为排名第i个的后缀的第一个字符在原字符串中的序号。如上面的sa[1]=2,因为后缀从序号2开始的aabbc排第一个。
——>定义rank[i]为序号从i开始的后缀排第几个。如上面的rank[1]=6,因为序号从1开始的daabbc排名第六个。有性质:rank[sa[i]]=i,sa[rank[i]]=i。
如何快速求得sa(因为根据性质,知道sa就知道rank),O(n)的方法DC3,O(n log n)的方法倍增算法。
因为大部分题目倍增算法就绰绰有余了。首先给原串中每一个字符排序:4,1,1,2,2,3(d,a,a,b,b,c),然后第一次合并隔0个合并:41,11,12,22,23,30;然后简化:6,1,2,3,4,5;第二次隔一个合并:62,13,24,35,40,50,然后简化:6,1,2,3,4,5,然后隔两个合并,然后隔四个合并,八个,十六个,直到大于n为止,道理很显然。由于空间足够,用O(n)的基数排序。是个二元组排序。

Code

void getsa(){
    int i,j,k,l;
    for(k=1;k<n;k++){
        if(k==1)j=0;else if(k==2)j=1;else j*=2;
        if(j>n)break;
        memset(js,0,sizeof(js));
        fo(i,1,n) js[rank[i+j]]++;
        fo(i,1,da) js[i]+=js[i-1];//用前缀和可以处理有多少个比自己小
        fod(i,n,1) zhi[js[rank[i+j]]--]=i;
        //次元的排序,zhi是次元的"sa"
        memset(js,0,sizeof(js));
        fo(i,1,n) js[rank[i]]++;
        fo(i,1,da) js[i]+=js[i-1];
        fod(i,n,1) sa[js[rank[zhi[i]]]--]=zhi[i];
        //主元的排序
        fu[sa[1]]=1;int o=1;
        fo(i,2,n) fu[sa[i]]=(rank[sa[i-1]]!=rank[sa[i]]||rank[sa[i]+j]!=rank[sa[i-1]+j])?(++o):o;
        //处理相同的情况
        swap(fu,rank);
    }
}

处理完sa和rank之后,才是后缀数组的核心部分。
——>定义数组height[i],表示排名已第i位的后缀与排名第i-1位的后缀的公共前缀的长度。如上面height[4]=1,因为bbc和bc的公共前缀的长度为1。
但是如何快速的求height[i],现在引进一个数组h[i]=height[rank[i]],表示当前这个以序号i开头的后缀与排名前一个的后缀的公共前缀的长度。
有一个重要的性质:h[i]>=h[i-1]-1。证明:如h[i-1]>=1(h[i-1]>=0很显然)设s[k-1..k-1+h[i-1]-1]=s[i-1..i-1+h[i-1]-1],所以s[k…k+h[i-1]-1]=s[i…i+h[i-1]-1]所以h[i]至少为h[i-1]-1。

Code

    k=0;
    fo(i,1,n){
        if(rank[i]==1) continue;
        while((i+k<=n)&&(s[i+k]==s[sa[rank[i]-1]+k]))k++;
        height[rank[i]]=k;
        if(k>0) k--;
    }

以上都是创建后缀数组的过程

应用

用height数组可以直接处理出,连着排名的两个后缀的最长公共前缀,所以求最大值就是后缀的最长公共前缀和任意两个后缀的最长公共前缀(可重叠的最长重复子串)。
给height的值按k分一下块,可以求出重复次数至少为k的子串个数。如0,2,1,2,3,1,1,2,按2分块为[1,2],[3,4,5],[6],[7,8],例如[3,4,5]表示排名3到5的最长公共前缀长度至少为2。如果要看不可重叠的重复k次子串,就加一个二分,再看每个块中的sa值的最大值和最小值之差是否不小于k[可重叠的很显然]。
如果要判回文串,就把串倒过来中间加个‘#’,‘¥’,‘$’之类的接起来,求一下height,穷举每一位,以这一位为中心,时效O(n log n)。
求不同的子串的个数,按照sa的顺序逐个加入子串,然后会贡献出n-sa[i]+1个新的前缀,因为有height[i]个相同的,再减去height[i]即可,道理很显然,这个跟上一个不同的前缀,肯定与上上个以及之前的不同。
多子串问题,大多都只要连起来,再应用上面的即可。

你可能感兴趣的:(字符串,后缀数组)