后缀数组

发布时间 2023-07-13 21:24:35作者: NuclearReactor

构造后缀数组

我们会得到两个数组 \(sa_i\) 表示排名为 \(i\) 的后缀是哪一个,\(rk_i\) 表示后缀 \(i\) 的排名。

这里只讲倍增做法。

原理非常简单,如下图:

实现需要一个对两位关键字进行排序的基数排序,设 \(y_i\) 表示按照第二维关键字排序,排名为 \(i\) 的是哪一个位置。设 \(rk_i\) 表示第 \(i\) 个位置,第一维关键字排序,排名是多少。

我们按照 \(i\) 从小到大把 \(rk_{y_i}\) 加入我们的桶中(我们用桶排来实现),对桶做前缀和之后,从大到小枚举 \(n\),按照 \(rk_{y_i}\) 来重新计算每个位置的排名。

这样做正确性显然,考虑如果两个位置第二维关键字排名不同,而第一维相同,那么排名越大的会先被枚举到,导致排名变大。如果第一维就不同,那么第一维更优的显然会排名更小。

所以就可以简单构造了,具体代码请到代码仓库。

求 height

LCP 的定义是最长公共前缀,以下用 \(LCP(i,j)\) 表示编号为 \(i\) 的后缀和编号为 \(j\) 的后缀的 LCP,我们令 \(Height_i=LCP(sa_i,sa_{i-1})\),而 \(Height_1\) 可以视作 \(0\)。我们考虑如何求 \(Height\) 数组。

  • 引理:\(Height_{rk_i}\ge Height_{rk_{i-1}}-1\)

证明:如果 \(Height_{rk_{i-1}-1}\) 小于等于 \(1\),那么上面这个式子显然成立,这是因为 \(Height_i\ge 0\)

如果 \(Height_{rk_{i-1}-1}>1\),我们考虑设后缀 \(i-1\)\(aAD\),那么后缀 \(i\) 就是 \(AD\),其中 \(A\) 是一个长度为 \(Height_{rk_{i-1}}-1\) 的字符串,由于 \(Height\) 数组的定义,我们考虑后缀 \(sa_{rk_{i-1}-1}\) 这个字符串一定是 \(aAB\) 的形式,所以 \(sa_{rk_{i-1}-1}+1\) 这个后缀一定是 \(AB\) 的形式,所以这个后缀一定排在后缀 \(i\) 的前面,所以我们可以知道 \(Height_{rk_i-1}\) 一定包含字符串 \(A\),所以结论成立。

我们利用上面这个东西就可以 \(O(n)\) 的来求 \(Height\) 数组了。

有一个结论是 \(LCP(sa_i,sa_j)=\min_{i+1\le k\le j}Height_k\),这个可以感性理解一下,如果 \(Height\) 一直大于某个值,那么这一段就一直有,反之,肯定是在一个后缀中改变。