后缀数组SA
一些约定
- 字符串下标从\(1\)开始。
- 字符串\(s\)的长度为\(n\)。
- "后缀\(i\)"代指以第\(i\)个字符开头的后缀
后缀数组:
主要有两个数组:\(sa\)和\(rk\)。
其中,\(sa_i\)表示将所有后缀排序后第\(i\)小的后缀的编号;
\(rk_i\)表示后缀\(i\)的排名。
两个数组满足一个性质\(sa[rk[i]]=rk[sa[i]]=i\)比较显然(排名为后缀\(i\)的排名的后缀编号为\(i\),另一个同理)
height数组:
\(height[i]=lcp(sa[i],sa[i-1])\),即第\(i\)名的后缀与第\(i-1\)名的后缀的最长公共前缀
一个重要的引理为:
\(height[rk[i]]\ge height[rk[i-1]]-1\)
证明:因为\(i, i-1\)只差一位,所以一定存在一个前缀与\(i\)有长度为\(height[rk[i-1]]-1\)的前缀,又因为排序相邻的两个前缀的\(lcp\)一定大于等于其他前缀,所以该引理正确
基数排序:
就是对于有\(k\)个关键字的元素进行排序,首先按照第一个关键字排序,分出大概的\(1,2,3,\dots\)排名,此时将排名相同的元素再按照第二个关键字排序,最终排完。
拿\(2\)个关键字举例:需要一个桶\(c\)放置第一关键字出现的次数,将第一关键字放完了以后,对桶\(c\)求前缀和,此时桶里的数值就是下标的排名;还需要一个数组\(y\),\(y_i\)表示第\(i\)小的第二关键字的下标是什么。然后从大到小遍历\(y_i\),把\(y_i\)代表的坐标的排名设为\(c[x[y[i]]]\),然后将\(c[x[y[i]]]-1\)即可
基于上述知识,以下将介绍后缀数组和\(height\)的求法了:
后缀数组: 利用基数排序,如果有一个\(x\)数组,其中\(x_i\)代表的是\(s[i,i+k-1]\)的在所有长度为\(k\)的子串排序后的排名,那么就可以将\(x_i\)作为第一关键字,\(x_{i+k}\)作为第二关键字,进行基数排序,最终就可以得到所有长度为\(2k\)的子串的排名了。
\(x\)数组好说,问题在于\(y\)数组怎么\(O(n)\)求。
- 能发现,\(len\le k\)的一个后缀是没有第二行关键字的,也就是相当于其第二关键字为\(0\),排名为\(1\),所以可以直接把这些后缀加入\(y\)数组里,但是排名是不能重复的,所以可以随便给他们一个排名,只需要让他们在前\(k\)个里面就好了,因为最终还是会按照第一关键字进行排序,所以正确性不会受到影响
- 对于其他的子串,因为需要按照第二关键字排序,且长度为\(k\)的子串已经排好了,所以可以拿来直接用,那么从小往大枚举长度为\(k\)的子串的排名为\(i\)的位置,即\(sa_i\),那么如果\(sa_i>k\)则\(x[sa[i]-k]\)为第一关键字,\(x[sa[i]]\)为第二关键字,就可以直接把\(sa[i]-k\)塞到\(y\)数组里了(如果让一个\(i\)作第二关键字,则\(i-k\)就是第一关键字,前提是\(i>k\))
所以就解决了。
for(int k = 1; k <= n; k <<= 1) {
for(int i = n - k + 1; i <= n; i ++) {
y[++ num] = i;// 没有第二关键字的直接放
}
for(int i = 1; i <= n; i ++) {
if(sa[i] > k) {
y[++ num] = sa[i] - k;
}
}
for(int i = 1; i <= n; i ++) c[i] = 0;// 清空桶
for(int i = 1; i <= n; i ++) c[x[i]] ++;//塞入桶
for(int i = 1; i <= n; i ++) c[i] += c[i - 1];//前缀和
for(int i = n; i >= 1; i --) {
sa[c[x[y[i]]] --] = y[i], y[i] = 0;//更新排名
}
num = 0;
swap(x, y);//相当于把y数组变成x,x清空,此时y中存的是i,i+k-1的子串的排名
x[sa[1]] = 1;// 排名为1的长度为2k的子串的排名为1,毋庸置疑,但是sa[2]可能和sa[1]相同就不能确定了
for(int i = 2; i <= n; i ++) {
x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++ num;
}// 更新x,把x更新为长度为2k的子串的排名
if(num == n) return;// 分出n个排名就结束
}

浙公网安备 33010602011771号