后缀数组初探

前言:JSOI2017round2day2T1后缀数组模板题一点都没看出来。不然就翻盘了

  痛定思痛后决定恶补字符串。

 

后缀数组

全是论文上抄来的。

学了一周,终于搞懂一些了。

1.基本定义

  字符串S;

  后缀:从位置i开始到结尾的子串,记作Suffix(i)=s[i..len[s]];

  后缀数组sa[1..n]

  表示1..n的一个排列,满足Suffix(sa[i])<Suffix(sa[i-1])

  即:在所有后缀中排第i的是谁

  名次数组rank[1..n]

  表示Suffix(i)在后缀中的名次。

  即:后缀i排第几。

  所以sa与rank为逆运算。

2.倍增算法求后缀数组。

  //学过DC3邪教的自行离开。其实我是用后缀自动机构造的。

  算法非常好理解,就是用倍增求出每个字符开始长度为2^k的子字符串排序

  k==0..log2n上取整, 最后一遍处理时,子串相当于后缀。

  代码:

 

 1 void suffix(){
 2     register int i,k;
 3     memset(cnt,0,sizeof(cnt));
 4     for (i=1;i<=l;i++)    cnt[s[i]]++;
 5     for (i=1;i<=MAXASCLL;i++)    cnt[i]+=cnt[i-1];
 6     for (i=l;i>0;i--) sa[cnt[s[i]]--]=i;
 7     rank[sa[1]]=1;
 8     for (i=2;i<=l;i++)    rank[sa[i]]=rank[sa[i-1]]+(s[sa[i]]!=s[sa[i-1]]);
 9     for (k=1;rank[sa[l]]<l;k*=2){
10         for (i=1;i<=l;i++) x[i]=rank[i];
11         for (i=1;i<=l;i++) y[i]=(i+k<=l)?rank[i+k]:0;//x为第一关键字,y为第二关键字
12         memset(cnt,0,sizeof(cnt));
13         for (i=1;i<=l;i++) cnt[y[i]]++;
14         for (i=1;i<=l;i++) cnt[i]+=cnt[i-1];
15         for (i=l;i>0;i--) say[cnt[y[i]]--]=i;
16         memset(cnt,0,sizeof(cnt));
17         for (i=1;i<=l;i++) cnt[x[i]]++;
18         for (i=1;i<=l;i++) cnt[i]+=cnt[i-1];
19         for (i=l;i>0;i--) sa[cnt[x[say[i]]]--]=say[i];//基数排序,先排y,再排x
20         rank[sa[1]]=1;
21         for (i=2;i<=l;i++) rank[sa[i]]=rank[sa[i-1]]+(x[sa[i]]!=x[sa[i-1]]||y[sa[i]]!=y[sa[i-1]]);    
22     }
23 }
View Code

 

 

 

 这里有个小优化:若rank[sa[n]]已经为n时(所有串已经分出大小),就退出。

时间复杂度:O(n log n);不要尝试跑1e6的构造数据(字符基本相同),常数巨大QAQ

3.height数组(最长公共前缀lcp)

  学会了构造,就要用后缀数组搞事情了。

  最经典的运用就是height数组。

  定义height[i]=Suffix(sa[i])与Suffix(sa[i-1])的最长公共前缀。

  对于Suffix(j),Suffix(k); rank[j]<rank[k];

  他们的最长公共前缀为min(height[rank[j]+1]..height[rank[k]]);

  毛想想,由于序列是升序的,那么数组的变换是不可逆(若sa[i],sa[i+1]的第j位发生了变化,那么sa[i],sa[i+1]的第j位(或以前的位)一定也变了)。

  那么问题就是如何快速求出height数组了。

  定义h[i]=height[rank[i]] Suffix(i)与前一名的最长公共前缀。

  举个例子:

  S=abaac

  Suffix=abaac, baac, aac, ac, c

  sa=3,1,4,2,5

  rank=2,4,1,3,5

  height=0,1,1,0,0

  h=1,0,0,1,0

  h数组有以下性质: h[i]>=h[i-1]+1;

  证明: 设Suffix(k)是排在Suffix(i-1)前一名的后缀(rank[k]=rank[i-1]-1)

  则它们的最长公共前缀为h[i-1],

  若h[i-1]<2, 那么显然成立,当h[i-1]>=2时,S[i-1..i]==S[k..k+1],

  且应为k在i-1的前一位,所以k+1排在i的前一位。

  所以至少要共享后h[i-1]-1位

void makeheight(){
    register int i,k=0,j;
    for (i=1;i<=l;i++){
        k=(k>0)?k-1:0;
        if (rank[i]==1){
            height[rank[i]]=0; k=0;
            continue;
        }
        for (j=sa[rank[i]-1];(j+k<=l)&&(i+k<=l)&&(s[j+k]==s[i+k]);k++);
        height[rank[i]]=k;
    }
}
View Code

  有了height数组,就可以用rmq求出lcp

  例题:[poj2774]求两个串的最长公共子串(l<100000)

  解法:将两串相连,在中间加一个没用到的字符,求出height数组,在其中找一个最大的height,且所指的两个串一个在空字符前一个在空字符后。

posted @ 2017-05-20 09:24  Vanisher  阅读(186)  评论(0编辑  收藏  举报