后缀数组基础

· · 个人记录

PDF

后缀数组 sa 和排名数组 rk

例题一:后缀排序。

sa_i 为把字符串 s 的所有后缀排序后,排名第 i 的后缀。例题要求的就是 sa.

rk_i 为把字符串 s 的所有后缀排序后,后缀 i 的排名。

根据定义,得

sa_{rk_i}=rk_{sa_i}=i.
sark\operatorname O(n \log n)

如果直接排序是 \operatorname O(n^2\log n) 的,不能接受。

注意到我们排序的元素有一些“包含”。使用倍增思想来排序。

rk_{w,i} 为子串 s_{i\sim i+2^w-1} 的排名。那么每次倍增合并后缀的排名,使用双关键字排序,最后得出的就是 rk 数组。

如图所示(《后缀数组——处理字符串的有力工具》,有改动)

那么内层排序我们使用基数排序和计数排序,就可以实现 \operatorname O(n \log n) 排序。

小优化:发现第二关键字无需计数排序,可以直接按值加入(详见代码)。

m = 256; // m refers to the maximum of the value of rk
for (int i=1; i <= n; i++) b[rk[i] = s[i]]++; // b is a bucket
for (int i=1; i <= m; i++) b[i] += b[i-1];
for (int i=n; i >= 1; i--) sa[b[rk[i]]--] = i;

for (int w=1; w <= n; w <<= 1)
{
    // The first key word is rk[i] while the second is rk[i+w]
    // The second key word doesn't need counting sort
    int c0 = 0;
    for (int i=n-w+1; i <= n; i++) c[++c0] = i; // [n-w+1,n] don't have the second key word, so they're the smallest
    for (int i=1; i <= n; i++) // i refers to the value of the second key word
        if (sa[i] > w) c[++c0] = sa[i]-w; // sa[i] is the position of i
    // Then we get an array c sorted by the second key word

    // Sort the first key word
    clear(b);
    for (int i=1; i <= n; i++) b[rk[i]]++;
    for (int i=1; i <= m; i++) b[i] += b[i-1];
    for (int i=n; i >= 1; i--) sa[b[rk[c[i]]]--] = c[i]; // Two-key-word counting sort
    memcpy(old,rk,sizeof old); // old rk

    // Compute new rk
    for (int i=1; i <= n; i++)
    {
        if (old[sa[i]] == old[sa[i-1]] && old[sa[i]+w] == old[sa[i-1]+w]) rk[sa[i]] = rk[sa[i-1]];
        else rk[sa[i]] = rk[sa[i-1]]+1;
    }
}
简单应用

例题二:字符加密。

题目大意:求字符串 s 最小的循环同构串。

把原字符串复制一遍,就变成了后缀排序问题。

例题三:【模板】AC自动机。

题目大意:给出文本串 s 和多个模式串 t_i,求 ts 中有没有出现。

因为我找不到别的题了,所以就只能用这一题。

t_is 中有出现,那么它一定是一个后缀的前缀。那么我们在 sa 上二分,找到最小的 \geqslant t_i 的后缀即可。

时间复杂度为 \operatorname O(|t_i|\log n),比 AC 自动机多一个 \log,但是在线。

高度数组 height(以下有时简称为 h

height_i=\text{lcp}(sa_i,sa_{i-1}),即排名i 的后缀和排名i-1 的后缀的最长公共前缀(的长度) 。

height 数组

引理一

h_{rk_i}\geqslant h_{rk_{i-1}}-1.

证明:当 h_{rk_{i-1}}\leqslant 1 时,引理成立。

h_{rk_{i-1}}\geqslant 1 时,即 \text{lcp}(i-1,sa_{rk_{i-1}-1})\geqslant 1.

设后缀 i-1\texttt{aAC},后缀 sa_{rk_{i-1}-1}\texttt{aAB},其中 \texttt{a} 为一个字符,\texttt{A}\texttt{B}\texttt{C} 均为子串,\texttt{B} 可为空。显然 \texttt{B} < \texttt{C}.

则后缀 i\texttt{AC}. 所以 \texttt{AB} < \texttt{AC} = 后缀 i.

又因为后缀 sa_{rk_{i}-1} 是最大的小于后缀 i 的后缀,所以 \texttt{AB}\leqslant sa_{rk_i-1} <\texttt{AC}= 后缀 i.

所以 \text{lcp}(i,sa_{rk_i-1})\supseteq\texttt{A}. 所以 h_{rk_i}\geqslant h_{rk_{i-1}}-1.

那么有了这个引理,就可以求出 height 数组了。

for (int i=1, k=1; i <= n; i++)
{
    if (k) k--;
    while (s[i+k] == s[sa[rk[i]-1]+k]) k++;
    height[rk[i]] = k;
}
##### 求子串的最长公共前缀 引理二 $$ \begin{aligned} \text{lcp}(sa_i,sa_j)=\min\left\{\begin{aligned} \text{lcp}(sa_i,sa_k), \\ \text{lcp}(sa_k,sa_j). \end{aligned} \right.\qquad i\leqslant k\leqslant j \end{aligned} $$ **证明**:当 $i=k$ 或 $k=j$ 时,引理成立。 当 $\text{lcp}(sa_i,sa_j)=0$ 时,引理成立。 否则,设 $$ \begin{aligned} &\text{lcp}(sa_i,sa_k)=\texttt{A},~\text{lcp}(sa_k,sa_j)=\texttt{B},~\text{lcp}(sa_i,sa_j)=\texttt{C}, \\ &|\texttt A|=a,~|\texttt{B}|=b,|\texttt{C}|=c. \end{aligned} $$ 异德 $c\geqslant \min(a,b)$,$\texttt{A}<\texttt{B}$. 假设 $c>a$,如图所示,则 $\texttt{C}\subset \texttt{A}$. ![](https://cdn.luogu.com.cn/upload/image_hosting/14c5xyzr.png) 容易看出 $\texttt{C-A}\leqslant \texttt{D}\leqslant \texttt{C-A}$,所以 $\texttt{A}=\texttt{C}$,与 $c>a$ 矛盾。 $c>b$ 的情况同理。那么引理得证! 所以得到定理一 $$ \begin{aligned} \text{lcp}(sa_i,sa_j) &= \min(\text{lcp}(sa_i,sa_{i+1}),\text{lcp}(sa_{i+1},sa_j)) \\ &= \min(\text{lcp}(sa_i,sa_{i+1}),\min(\text{lcp}(sa_{i+1},sa_{i+2}),\text{lcp}(sa_{i+2},sa_j)) \\ &\cdots \\ &= \min_{k=i+1}^j height_k. \end{aligned} $$ 那么就可以把求后缀的 $\text{lcp}$ 转化为 $height$ 数组的 RMQ 了。 可以把求子串的 $\text{lcp}$ 转化为求后缀的 $\text{lcp}$,再与子串长度取 $\min$. ##### 简单应用 **例题四**:给出 $n$ 个字符串 $s_1\sim s_n$,求 $$ \sum_{i=1}^n \sum_{x=1}^{|s_i|}\sum_{j=1}^n\sum_{y=1}^{|s_j|}|\text{lcp}(s_{i,x\sim |s_i|},s_{j,y\sim |s_j|}) |. $$ 即求任意两个后缀的最长公共前缀的长度之和。$\sum |s_i|\leqslant 10^6$. 把所有串拼起来,用不同的特殊字符间隔,然后求后缀数组。 对于 $height_i$,设区间 $[l_i,r_i]$ 是 $height$ 上最大的区间满足以 $height_i$ 为最小值。则 $\forall i\in[l_i-1,i-1]$,$\forall j\in[i,r_i]$,后缀 $sa_i$ 和后缀 $sa_j$ 的最长公共前缀都为 $height_i$. 那么预处理 $l_i,r_i$,对于每个 $height_i$ 乘一下即可。 也可以全部用 `#` 间隔,但是 $height_i$ 要对真实后缀长度取 $\min$. **例题五**:[GDOI 2015] 短信加密。 题目大意:给出一个字符串 $s$,对于 $s$ 的 $k$($k\geqslant 2$) 个不重叠且相同子串 $t$,它的代价为 $k|t|$. 求最大的 $k|t|$ 和相应的字典序最小的 $t$. $n\leqslant 10000$. 重复出现子串就相当于两个后缀的 $\text{lcp}$. 枚举 $|t|=l$. 把 $height$ 分块,满足每块内的 $height$ 值都 $\geqslant l$,有些后缀不属于任何一个块。根据 $height$ 的定义和引理二,块内的任意两个后缀的 $\text{lcp}$ 都 $\geqslant l$,块外两个都 $\leqslant l$. 那么我们贪心地求 $t$. 异德 $t$ 越靠前,$k$ 越大。所以将块内的 $sa$ 排序,必须选第一个,能选的尽量选。