后缀数组
后缀数组是处理字符串的利器。
定义
对于长度为 \(n\) 的字符串 \(s = s_1 s_2 \cdots s_n\),定义 \(s[i,j] = s_i s_{i+1} \cdots s_j\),即 \(s\) 的第 \(i\) 个到第 \(j\) 个字符形成的子串。
定义 sa 为 \(s\) 的后缀数组,sa[i] 表示第 \(i\) 小的后缀的编号。
定义 rk[sa[i]]=i,即 rk[i] 表示后缀 \(i\) 的 rank。
求解
考虑最暴力的做法,取出所有后缀并排序,空间复杂度 \(O(n ^ 2)\),时间复杂度 \(O(n^2 \log n)\)。
考虑倍增,设 rk[j][i] 表示在所有长度为 \(2^j\) 的后缀中,后缀 \(s[i,i+2^j-1]\) 的 rank(超出 \(n\) 的部分记为空字符,小于任何非空字符)。
显然我们可以 naively 得到 rk[0][i],考虑如何由 rk[j-1] 得到 rk[j]。
发现可以将所有二元组 (rk[j-1][i],rk[j-1][i+(1<<(j-1))] 排序,即把每段后缀分为前后两段,并以前一半的 rank 作为第一关键字,后一半的 rank 作为第二关键字,超出 \(n\) 的部分记为 0,排序后得到 rk[j]。
总共倍增 \(\mathrm O(\log n)\) 次,每次排序 \(\mathrm O(n \log n)\),总时间复杂度 \(\mathrm O(n \log^2 n)\)。
发现 rk 的值域是 \(\mathrm O(n)\) 的,因此可以用桶排做到 \(O(n \log n)\)。
在算法竞赛中这已经足够了,如果追求更优的时间复杂度,可以考虑 SA-IS / DC3 算法,线性时间复杂度,但实现十分复杂,在竞赛中不常用,因此不再赘述。
显然通过 rk 可以 \(\mathrm O(n)\) 得到 sa。
height 数组
定义 ht[i] 为后缀 sa[i] 与 sa[i-1] 的最长公共前缀的长度,并设 ht[1]=0。
一眼看来似乎没有线性做法,但是我们将尝试证明以下引理:
Lemma: ht[rk[i]] \(\ge\) ht[rk[i-1]]-1。
Proof:
ht[rk[i-1]] \(\le 1\) 的情况是 naive 的。
若 ht[rk[i-1]] \(\ge 2\),即 sa[rk[i-1]-1] 与 i-1 的最长公共前缀长度 \(\ge 2\),设这个最长公共前缀为 \(aA\),其中 \(a\) 是单个字符,\(A\) 是一个非空字符串。
那么设后缀 sa[rk[i-1]-1] 为 \(aAB\),i-1 为 \(aAC\),则 \(B \lt C\),且后缀 sa[rk[i-1]-1]+1 为 \(AB\),i 为 \(AC\)。
后缀 sa[rk[i]-1] 因为只比 i 前一名,所以是小于 i 中最大的,又因为 \(AB \lt AC\),所以 \(AB \le\) 后缀 sa[rk[i]-1] \(\lt AC\),容易看出后缀 i 和后缀 sa[rk[i]-1] 有公共前缀 \(A\)。
于是 ht[rk[i]] $\ge |A| = $ ht[rk[i-1]]-1。
\(\square\)
由此,我们从 ht[rk[i-1]]-1 开始暴力拓展 ht[rk[i]],在 \(\mathrm O(n)\) 的时间复杂度内得到 ht。
ht 数组有这样一个性质:后缀 i,j 的最长公共前缀长度等于 ht[rk[i],rk[j]) 中的最小值。(钦定 rk[i] \(\lt\) rk[j])
因此我们可以用 ST 表 \(\mathrm O(n \log n)\) 预处理后 \(\mathrm O(1)\) 询问任意后缀的最长公共前缀长度。
例题
UOJ 35.后缀排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54  |  |