后缀数组
后缀数组
对于给定的字符串\(s\),构建两个数组\(sa\)和\(rk\),其中\(rk[i]\)表示\(s[i,n]\)在\(s\)的所有后缀中的字典序排名,\(sa[i]\)则表示排名\(i\)的后缀的最左端位置。容易发现有\(sa[rk[i]]=rk[sa[i]]=i\)。
构造 - 倍增法
一般来说,我们用倍增法\(O(n\log n)\)构建出\(rk\)数组就可以了。
我们倍增考虑的长度\(k\)。当\(k>=n\)时退出。
- 每次倍增开始前,你要保证这些后缀的排名是将他们“长度为\(k\)的前缀”排序后的排名。若两个字符串“长度为\(k\)的前缀”相同,则排名也应相同。
- 然后把每个后缀\(i\)“以\(rk[i]\)为第一关键字、\(rk[i+k]\)为第二关键字”再次排序,更新\(rk\)数组。(对于越界的\(rk\)下标,你认为其值为\(-\infty\))这样你就得到了按“长度为\(2k\)的前缀“排序后的排名辣!
最开始时把所有后缀按它们的首字母排序,获得初始排名\(rk\)数组即可。
排序过程采用基数排序,则总复杂度为\(O(n\log n)\)。
举个栗子:
b a n a n a
2 1 3 1 3 1 // k=1
3 2 4 2 4 1 // k=2
3 2 5 2 4 1 // k=4
4 3 6 2 5 1 // k=8
用途 - LCP(最长公共前缀)
我们令\(lcp(s_1, s_2)\)为字符串\(s_1, s_2\)的最长公共前缀。令\(LCP(i,j)=lcp(s[sa[i],n], s[sa[j],n])\)。
那么我们有引理\(LCP-Lemma\):\(\forall i<j<k, LCP(i,k)=\min(LCP(i,j),LCP(j,k))\)
这样我们就得到了推论:\(LCP(i,j)=\sum\limits_{k=i+1}^j LCP(k-1,k)\),原问题转为\(RMQ\)。但不幸的是,\(LCP(k-1,k)\)你还只会\(O(n^2)\)预处理,这显然是不行的。
height 数组
我们设\(height[i]=LCP(i,i-1), h[i]=height[rk[i]]\),则\(height[1]=0\)。考虑如何高效求\(h\)数组。
有一个结论\(LCP-Theorem\):\(h[i]\ge h[i-1]-1\)
根据\(LCP-Theorem\),每次\(h[i]\)直接从\(h[i-1]-1\)开始大力枚举就好。复杂度\(O(n)\)。
再举一个栗子:
s: b a n a n a
h: 0 3 2 1 0 0
a -> 0
ana -> 1
anana -> 3
banana -> 0
na -> 0
nana -> 2
引理及证明
引理LCP-Lemma
\(\forall i<j<k, LCP(i,j)=\min(LCP(i,j),LCP(j,k))\)
证明:设\(p=\min(LCP(i,j),LCP(j,k))\),则我们显然有\(p\ge LCP(i,k)\)。
结合\(i<j<k\),我们可以知道:\(s[sa[i]+p]<s[sa[j]+p]\)和\(s[sa[j]+p]<s[sa[k]+p]\)中至少一个成立。否则\(p<\min(LCP(i,j),LCP(j,k))\)并产生矛盾。
所以\(s[sa[i]+p]<s[sa[k]+p]\),故\(p\le LCP(i,k)\)。证毕。
定理LCP-Theorem
\(h[i]\ge h[i-1]-1\)
证明:当\(h[i-1]=0\)时,显然成立。下面讨论\(h[i-1]>0\)的情况。
设\(k=sa[rk[i-1]-1]\),则\(h[i-1]=lcp(s[k,n], s[i-1,n])\),那么有\(\left\{\begin{array}{**lr**} h[i-1]-1=lcp(s[k+1,n], s[i,n])\\ rk[k+1]<rk[i] \end{array}\right.\)(这一步相当于把他们的首字符砍掉)。
结合\(LCP-Lemma\),可得