定义
将一个字符串的每个后缀按字典序排序,后缀数组 $ sa_i $ 表示排名为 $ i $ 的后缀的起始下标。
求法
将所有后缀快排,复杂度 $O(n^2\log n)$,太大。
正确做法为倍增+基数排序,复杂度 $O(n\log n)$。
首先比较每个后缀的第一个字符,得出第一关键字。
接着以第二个字符为第二关键字,注意这里我们存的是排名为 $i$ 的后缀的起始位置。
按照两个关键字排序,得到每个后缀的排名(可以并列),变为第一关键字。
接下来倍增,同时比较第三、四个字符,得出新的第二关键字。
注意到第三、四个字符的排名已经算好了,起始下标为 $i$ 的后缀,第三、四个字符的排名就是下表为 $i+2$ 的后缀的第一关键字。
如果没有三、四个字符,那么排名就最前(用 $0$ 表示)。
于是以此类推,每次比较到 $2^k$,直到排名互不相同,算法结束。
1 | inline void gsa() |
基数排序
复杂度 $O(n)$ ,类似桶排。
我们先把第一关键字装进桶里,做一个前缀和,就能知道每个桶里的数排名的上界。第二关键字是升序排序的,前面的排名较高。于是我们倒序遍历,从小到大出来,排名的上界也不断减小,就能成功排完。
1 | inline void ssort() |
应用:($LCP$)
显然除了模板题不可能出裸题。
因此我们需要利用一个叫 $LCP$ 的东西。
定义
两个字符串的最长公共前缀。
求法
这里我们需要一个数组 $height$,$height_i$ 即表示 $lcp(sa_i,sa_{i-1})$,即第 $i$ 名的后缀与它前一名的后缀的最长公共前缀。
一个引理:$height_{rk_i}\ge height_{rk_{i-1}}-1$。
其中 $rk_i$ 表示以 $i$ 为起始字符的后缀的排名。
利用这个引理,暴力就可以 $O(n)$ 求得 $height$。
1 | for (i = 1, k = 0; i <= n; i++) { |