后缀数组

对于一个字符串\(S\),我们记后缀 \(Suf_i\) 为从 \(i\) 开始一直到末尾所形成的子串(\(S_iS_{i+1}...S_{len}\))。

现在我们要对所有后缀按照字典序排序。记 \(sa_i\) 表示排名第 \(i\) 的后缀的起始位置,\(x[i]\) 表示 \(Suf_i\) 的排名。

考虑使用一种倍增的方法排序:一开始我们提取每个后缀的长度为 \(1\) 的字符进行排序;然后将每个后缀的第 \(1\) 个字符作为第一关键字,第 \(2\) 个字符作为第二关键字,对这些长度为 \(2\) 的字符串进行排序;然后将每个后缀的前 \(2\) 个字符作为第一关键字,第 \(3\) 和第 \(4\) 个字符作为第二关键字,将长度为 \(4\) 的字符串进行排序……

由于我们对每个后缀长度为 \(2n\) 的字符串排序时,长度为 \(n\) 的字符串的排名已经确定,所以这个算法的正确性是显然的。

如果用 \(sort\) 直接排序,加上倍增的一个 \(log\) 总复杂度是 \(O(nlog^2n)\) 的,考虑优化:由于只有两个关键字且值域范围较小,我们可以使用基数排序,一种 $O(n) $ 的排序方式。这个不太好讲解,最好靠自己理解,实在看不懂可以背。代码中 \(y\) 数组的含义是第二关键字排名第 \(i\) 的第一关键字的位置为 \(y_i\) (这好像是唯一能提供的帮助了)。

好了现在通过慢慢理解你现在求出了 \(sa\) 数组和 \(x\) 数组了,我们现在用他们来搞点事情(毕竟不可能让你排个序就完了)。

\(lcp(i,j)\)\(Suf_i\)\(Suf_j\) 的最长公共前缀,\(height_i\) 表示 \(lcp(sa_i,sa_{i-1})\)

假设我们现在已经拥有了 \(height\) 数组,考虑如何快速求 \(lcp\) :容易发现(假设\(x_j>x_i\)

\[lcp(i,j)=\min(height_{x[i]+1},height_{x[i]+2},...,height_{x[j]} ) \]

比较通俗的理解就是一个排名为 \(i\) 的字符串,相对于与排名为 \(j(j>i+1)\) 的串的 \(lcp\) 来说,与排名为 \(i+1\) 的串的 \(lcp\) 肯定不会更短。

有了这个性质以后,我们发现求两个后缀的 \(lcp\) 就相当于求一个区间最小值,一个 \(ST\) 表就可以解决.

好了现在看怎么求 \(height\) 数组:

\(h_i=height_{x[i]}\),人话的意思就是 \(h_i\) 表示 \(Suf_i\) 与它排名前一位的后缀的 \(lcp\)

可以发现一个关系:

\[h_i\geq h_{i-1}-1 \]

我们首先发现 \(Suf_i\)\(Suf_{sa[x[i-1]-1]+1 }\)\(lcp\) 一定为 \(h_{i-1}-1\)(前提是 \(h_{i-1}\neq 0\)),因为 \(h_i\geq h_{i-1}-1\) 这种关系我们可以直接 \(O(n)\) 推出 \(h\) 数组,在还原一下就能回到 \(height\) 数组了(\(height_i=h_{sa[i]}\))。

  • 例题:
  1. luogu P3809 【模板】后缀排序 题解
  2. luogu P3181 [HAOI2016]找相同字符 题解
  3. luogu P2852 [USACO06DEC]Milk Patterns G 题解
  4. luogu P5546 [POI2000]公共串 题解
posted @ 2020-06-18 07:43  With_penguin  阅读(77)  评论(0)    收藏  举报