后缀排序

发布时间 2023-07-27 09:42:51作者: He_Zi

后缀排序

本文做复习用,不宜初学用。

定义

\(sa\) 表示排名为 \(i\) 的位置。

\(rk\) 表示位置为 \(i\) 的排名。

\(y\) 表示按照第二关键字排序排名为 \(i\) 的位置。

\(height\) 表示排名为 \(i\)\(i - 1\) 的后缀的最大前缀

\(h\) 表示位置为 \(i\) 和它排名前一位的后缀的最大前缀

操作流程

最初字符串排序

这里是桶排序基础操作。

for(int i = 1; i <= n; ++i) ++c[rk[i] = a[i]];
for(int i = 1; i <= m; ++i) c[i] += c[i - 1];
for(int i = n; i >= 1; ++i) sa[c[rk[i]--] = i;//按照1,2,3的顺序排

为什么是倒叙的呢?你用手模拟一下ababa这个样例就懂了

进行倍增比较操作。

for(int i = 1; i <= n; i <<= 1)

第二关键字排序

这里是把最后那一部分没有第二关键字的放在最前面。

num = 0;
for(int i = n - k  + 1; i <= n; ++i) y[++num] = i;

对有第二关键字的排序。按照 \(sa\) 数组,也就是排名进行。

for(int i = 1; i <= n; ++i) if(sa[i] - k > 0) y[++num] = sa[i] - k;

总体排序

清空桶。

for(int i = 1; i <= m; ++i) c[i] = 0;

这里先按照 \(rk\) 排序,和最初字符串排序有点类似。

for(int i = 1; i <= n; ++i) ++c[rk[i]];
for(int i = 1; i <= m; ++i) c[i] += c[i - 1];
for(int i = n; i >= 1; --i) sa[c[rk[y[i]--] = y[i];//按照y[i]的顺序排

更新 \(rk\) 数组和 \(m\)

这里把 \(y\)\(rk\) 交换其实就是用 \(y\) 暂时存储上一次的 \(rk\)

比较当前 \(sa[i]\)\(sa[i - 1]\) 是否完全相等。如果相等就和 \(sa[i-1]\) 赋一样的 \(num\)

swap(rk,y);num = 0;
for(int i = 1; i <= n; ++i)
    rk[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && sa[i] + k <= n && sa[i - 1] + k <= n && y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++num;
if(num == n) break;
m = num; num;

LCP

定义

\(suff(i)\) 表示以 i 开头的后缀。

\(height[i]\) 表示排名为 \(i\) 与排名为 \(i-1\) 的后缀字符串的前缀。

\(h[i]\) 表示位置为 \(i\) 与它的排名\(-1\) 的后缀字符串的前缀。

形式化的:

\[height[i] = lcp(suff(sa[i]),suff(sa[i - 1]))\\ h[i] = height[rk[i]],height[i] = h[sa[i]] \]

有这样的好性质:

\[h[i] \ge h[i - 1] - 1 \\ lcp(suff(i),suff(j)) = \min_{x = rk[i] + 1} ^ {rk[j]}lcp(suff(sa[x]),suff(sa[x - 1])) = \min_{x = rk[i] + 1} ^ {rk[j]} height[x] \]

所以要算 \(lcp\) 直接用一个 \(RMQ\) 就可以了。

求 h 数组

这里很好理解。

for(int i = 1; i <= n; ++i){
		h[i] = h[i - 1] - 1;
		if(h[i] < 0) h[i] = 0;
		while(a[sa[rk[i]] + h[i]] == a[sa[rk[i] - 1] + h[i]] ) ++h[i];
	}