后缀数组-SA
引入
给出一个长为 \(n\) 的字符串 \(s\),要求每个后缀的排名。
约定
后缀 \(i\) 表示 \(s_i\) 开头的后缀,储存时用 \(i\) 直接代表 \(s_{i...n}\)。
\(sa_i\) 表示排名为 \(i\) 的后缀 \(x\)。
\(rk_i\) 表示后缀 \(i\) 的排名。
\(O(n^2\log n)\)
将所有后缀都弄下来,然后直接用 sort
排序。
\(O(n\log^2 n)\)
这里需要用到倍增的思想。
我们先对长度为 \(1\) 的串进行排序,求出 \(sa,rk\);
然后用 \((rk_i,rk_{i+1})\) 作为排序的第一、二关键字去排序,就能求出长度为 \(2\) 的串的 \(sa,rk\);
再用用 \((rk_i,rk_{i+2})\) 作为排序的第一、二关键字去排序,就能求出长度为 \(4\) 的串的 \(sa,rk\);
以此类推,就可以得到最后的排名。
对于 \(i+l>n\) 的,我们将 \(rk_{i+l}=0\) 即可,因为空串显然是最小的。
\(O(n\log n)\)
发现我们每次都是两个关键字进行排序,可以考虑基数排序。
定义 \(tp_i\) 表示后半部分(即 \(s_{x+l/2...x+l-1}\))的排名为 \(i\) 的后缀 \(x\)。
定义 \(cnt_i\) 表示当前排名 \(\le i\) 的串有多少个。(显然,如果每个串的排名都各不相同,那么就结束排序了)
过程:
-
先计算 \(tp\)
-
进行基数排序,利用 \(cnt\) 计算 \(sa\)
-
最后利用上一次的 \(rk\) 和刚计算得到的 \(sa\) 更新 \(rk\)
先讲最后一步:
-
显然 \(rk[sa_1]=1\)
-
对于 \(sa_i\),如果上一次的 \(rk[sa_i]=rk[sa_{i-1}], rk[sa_i+l]=rk[sa_{i-1}+l]\),那么 \(rk[sa_i]=rk[sa_{i-1}]\);否则 \(rk[sa_i]=rk[sa_{i-1}]+1\)
再来讲讲基数排序:
-
将 \(rk\) 加到对应的 \(cnt_i\) 中,并对 \(cnt\) 做前缀和
-
按照 \(tp\) 从大到小更新 \(sa\),因为对于相同的 \(rk\) (第一关键字),\(tp\) 越大,排名越靠后。每更新一个 \(sa\),将相应的 \(cnt\) 减一。
代码
后缀排序整体部分:
inline void sufsort()
{
m = 75;
FOR(i, 1, n) rk[i] = s[i] - '0' + 1, tp[i] = i;
Bsort();
for(int l = 1, cnt = 0; cnt < n; l <<= 1, m = cnt)
{
cnt = 0;
FOR(i, n - l + 1, n) tp[++cnt] = i;
FOR(i, 1, n) if(sa[i] > l) tp[++cnt] = sa[i] - l;
Bsort();
std::swap(rk, tp); rk[sa[1]] = cnt = 1;
// 注意这时 tp 是之前长度为 la 时的 rk
FOR(i, 2, n) rk[sa[i]] = (tp[sa[i]] == tp[sa[i - 1]] && tp[sa[i] + l] == tp[sa[i - 1] + l]) ? cnt : ++cnt;
}
}
基数排序部分:
inline void Bsort()
{
FOR(i, 1, m) tax[i] = 0;
FOR(i, 1, n) tax[rk[i]]++;
FOR(i, 1, m) tax[i] += tax[i - 1];
ROF(i, n, 1) sa[tax[rk[tp[i]]]--] = tp[i];
}
height 数组
LCP:最长公共前缀。
\(height_i=\text {lcp}(sa_i, sa_{i-1})\)
现在要 \(O(n)\) 求出 \(height\) 数组。
定理:\(height[rk_i] \ge height[rk_{i-1}]-1\)
证明:(参考自 OI-Wiki)
-
如果 \(height[rk_{i-1}]=1\),显然成立。
-
如果 \(height[rk_{i-1}]>1\):
-
说明 \(\text{lcp}(i-1,sa[rk_{i-1}-1])>1\)
-
我们设 \(aA\) 来表示它俩的最长公共前缀,设后缀 \(i-1\) 为 \(aAD\),后缀 \(sa[rk_{i-1}-1]\) 为 \(aAB\)。
-
那么后缀 \(i\) 可以表示为 \(AD\),而且一定会存在后缀 \(sa[rk_{i-1}-1]+1\) 为 \(AB\)。
-
因此 \(\text{lcp}(i,sa[rk_i-1])\) 至少为 \(A\)
-
所以有 \(height[rk_i] \ge height[rk_{i-1}]-1\)
有了这个结论,我们就可以暴力但 \(O(n)\) 求出 \(height\) 数组了:
inline void get_hei()
{
int k = 0;
FOR(i, 1, n)
{
if(k) k--;
while(s[i + k] == s[sa[rk[i] - 1] + k]) k++;
hei[i] = k;
}
}