Preface
我们希望将一个长度为n的字符串S的所有后缀按照字典序排序。
因为每个后缀的长度不相同,因此不会出现相等的情况
目标:求出两个数组SA[]和Rank[],SA[i]表示排第i名的是谁,Rank[i]则是以i这个位置开头的后缀排第几名
一般后缀排序有两种方法,倍增和DC3,这里只讲好打方便常用的倍增。DC3我并不会(据说DC3某些时候跑的比倍增还要慢)
进入正题
SA和Rank
我们设suf(i)表示第i个位置开头的后缀(即S[i..n])。
对于每个suf(i),定义它长度为k的前缀为suf(i,k)(即S[i..i+k-1])
显然有几个性质
suf(i,2k)<suf(j,2k) s u f ( i , 2 k ) < s u f ( j , 2 k ) 当且仅当suf(i,k)<suf(j,k) s u f ( i , k ) < s u f ( j , k ) 或suf(i,k)=suf(j,k) s u f ( i , k ) = s u f ( j , k ) 并且suf(i+k,k)<suf(j+k,k) s u f ( i + k , k ) < s u f ( j + k , k )
suf(i,2k)=suf(j,2k) s u f ( i , 2 k ) = s u f ( j , 2 k ) 当且仅当suf(i,k)=suf(j,k) s u f ( i , k ) = s u f ( j , k ) 并且suf(i+k,k)=suf(j+k,k) s u f ( i + k , k ) = s u f ( j + k , k )
设数组SAk[],Rankk[]
S
A
k
[
]
,
R
a
n
k
k
[
]
表示在只排序每个后缀长度为k的前缀条件下的SA,Rank
那么完全可以利用Rank来比较大小
然而,在k长度前缀下可能有完全相同的字符串,那么他们的Rank_k就是相等的
于是每个suf(i,2k),都有两个关键字Rankk[i] R a n k k [ i ] ,Rankk[i+k] R a n k k [ i + k ]
可以利用双关键字基数排序O(n)求出SA2k[] S A 2 k [ ] ,Rank2k[] R a n k 2 k [ ] ,再到4k,8k,16k,最后当k大于等于n了,SAk S A k 和Rankk R a n k k 就是SA和Rank了。那么排序就结束了(显然超出部分并不影响)
当然,初始时你需要对每一个字符做一遍排序,相当于k=1
事实上,整个倍增求SA的算法的核心就在于双关键字基数排序,它的实现特别的玄妙,
算法流程
首先,根据计算好的SA_k,Rank_k求出以suf(i+k,k)为关键字将i排序的数组s2_k[]
然后我们用计数排序的思想,开一个计数数组ct,ct[i]表示Rankk[]<=i R a n k k [ ] <= i 的后缀的数量,ct[i]-ct[i-1]为Rankk[]=i R a n k k [ ] = i 的后缀的数量(因为可以重复,所以可能有多个)
我们从n到1访问s2_k[i],那么新的第ct[Rankk[s2k[i]]]
c
t
[
R
a
n
k
k
[
s
2
k
[
i
]
]
]
名就是s2_k[i],也就是说SA2k[ct[Rankk[s2k[i]]]]=s2k[i]
S
A
2
k
[
c
t
[
R
a
n
k
k
[
s
2
k
[
i
]
]
]
]
=
s
2
k
[
i
]
(是不是有点绕。。)
然后ct[Rankk[s2k[i]]]−−
c
t
[
R
a
n
k
k
[
s
2
k
[
i
]
]
]
−
−
。
这个过程保证了第一关键字小的一定排名更小,第一关键字相同时第二关键字小的排名更小
之后,我们只需要通过计算好的新的SA_2k来计算Rank_2k
结合代码来理解一下
后缀排序部分代码
由于长度是2的幂次倍增上去的,因此只会做log n层,每层的复杂度都是O(n)的
因此总复杂度为O(n log n)
最长公共前缀(LCP)
然而,往往光有SA和Rank是并不够的,我们还需要一个height数组
设lcp(i,j)表示suf(i)和suf(j)的最长公共前缀长度 ,LCP(i,j)表示lcp(SA[i],SA[j])的长度(注意区分大小写,大写的表示第i名和第j名的lcp)。
设height[i]=LCP(i−1,i),且令height[1]=0
首先,十分显然的,对于任意i<=k<j
i
<=
k
<
j
,有LCP(i,j)<=LCP(i,k)
L
C
P
(
i
,
j
)
<=
L
C
P
(
i
,
k
)
。( 即字典序排名更接近后缀的lcp更长)
有定理:
对于任意1<=i<k<j<=n 1 <= i < k < j <= n ,LCP(i,j)=min(LCP(i,k),LCP(k,j)) L C P ( i , j ) = m i n ( L C P ( i , k ) , L C P ( k , j ) ) 均成立
这个也比较显然,随便举个例子画一画就可以看出。
上面的式子可以证明LCP定理、
对于i<k<=j,LCP(i,j)=min(height[k]) i < k <= j , L C P ( i , j ) = m i n ( h e i g h t [ k ] ) 。
有了这个东西,求两个后缀的lcp转化为了区间最小值,我们就可以用RMQ之类的算法快速完成了
(感性理解一下)
具体证法较为复杂,见
问题在于如何快速求height,当然可以二分+Hash,但这样太蠢了
我们有O(n)的做法
设h[i]=height[Rank[i]],也就是suf(i)和suf(i)的前一名的lcp。
显然h[SA[i]]=height[i]
有定理
若i>1且Rank[i]>1,则有h[i]≥h[i−1]−1
证明如下:
设j=SA[Rank[i]−1] j = S A [ R a n k [ i ] − 1 ] (suf(i)的前一名),k=SA[Rank[i−1]−1] k = S A [ R a n k [ i − 1 ] − 1 ] (即suf(i-1)的前一名)
则h[i]=lcp(j,i),h[i−1]=lcp(k,i−1) h [ i ] = l c p ( j , i ) , h [ i − 1 ] = l c p ( k , i − 1 )
若h[i−1]≤1 h [ i − 1 ] ≤ 1 ,定理显然成立,因为h[i]≥0≥h[i−1]−1 h [ i ] ≥ 0 ≥ h [ i − 1 ] − 1
若h[i−1]>1 h [ i − 1 ] > 1 ,即lcp(k,i−1)>1 l c p ( k , i − 1 ) > 1
对于x,y<N x , y < N ,如果有lcp(x,y)≥1 l c p ( x , y ) ≥ 1 ,则suf(x)和suf(y)的第一个字符相同
那么suf(x)<suf(y) s u f ( x ) < s u f ( y ) 等价于suf(x+1)<suf(y+1),lcp(x+1,y+1)=lcp(x,y)−1 s u f ( x + 1 ) < s u f ( y + 1 ) , l c p ( x + 1 , y + 1 ) = l c p ( x , y ) − 1
我们有lcp(k,i−1)>1,suf(k)<suf(i−1) l c p ( k , i − 1 ) > 1 , s u f ( k ) < s u f ( i − 1 )
因此suf(k+1)<suf(i) s u f ( k + 1 ) < s u f ( i ) (即Rank[k+1]≤Rank[i]−1 R a n k [ k + 1 ] ≤ R a n k [ i ] − 1 ),h[i−1]=lcp(k,i−1)=lcp(k+1,i)+1 h [ i − 1 ] = l c p ( k , i − 1 ) = l c p ( k + 1 , i ) + 1
又Rank[j]=Rank[i]−1 R a n k [ j ] = R a n k [ i ] − 1 ,联系之前的引理可得h[i]=lcp(j,i)≥lcp(k+1,i)=h[i−1]−1 h [ i ] = l c p ( j , i ) ≥ l c p ( k + 1 , i ) = h [ i − 1 ] − 1
得证!
求Height
有了这个性质,我们就可以有O(N) O ( N ) 的方法求出height h e i g h t
对于每一个h[i] h [ i ] ,我们直接从h[i−1]−1 h [ i − 1 ] − 1 的长度开始暴力判断lcp(sf(i),sf(SA[Rank[i]−1])) l c p ( s f ( i ) , s f ( S A [ R a n k [ i ] − 1 ] ) )
求出h h 后再根据heightheight和h h 的关系式求出heightheight
每次判断看似是O(N)的,然而,h最多-1 N次,右移最多是2N次,总复杂度仍是O(N)
下面是代码,真的非常短
代码
至此,大功告成
附上SA
S
A
模版
这里有一道SA
S
A
的应用题,大家可以做一做
JZOJ[1598]文件修复,下面附上链接的题意和讲解