后缀数组基础

后缀数组 s a sa sa 和排名数组 r k rk rk

例题一:后缀排序

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

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

显然
s a r k i = r k s a i = i . sa_{rk_i}=rk_{sa_i}=i. sarki=rksai=i.

s a sa sa r k rk rk O ⁡ ( n log ⁡ n ) \operatorname O(n \log n) O(nlogn)

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

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

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

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

image-20230428190939431

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

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

m = 256; // m represent the range of the value of rk
for (int i=1; i <= n; i++) b[rk[i] = s[i]]++;
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 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 is the value of the second key word
        if (sa[i] > w) c[++c0] = sa[i]-w; // sa[i] is the position of i
    // 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;
    }
    m = c0;
}

简单应用

例题二:字符加密

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

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

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

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

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

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

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

高度数组 h e i g h t height height( 以下有时简称为 h h h

h e i g h t i = lcp ( s a i , s a i − 1 ) height_i=\text{lcp}(sa_i,sa_{i-1}) heighti=lcp(sai,sai1),即排名 i i i 的后缀和排名 i − 1 i-1 i1 的后缀的最长公共前缀。

h e i g h t height height 数组

引理一
h r k i ⩾ h r k i − 1 − 1. h_{rk_i}\geqslant h_{rk_{i-1}}-1. hrkihrki11.
证明:当 h r k i − 1 ⩽ 1 h_{rk_{i-1}}\leqslant 1 hrki11 时,引理成立。

h r k i − 1 ⩾ 1 h_{rk_{i-1}}\geqslant 1 hrki11 时,即 lcp ( i − 1 , s a r k i − 1 − 1 ) ⩾ 1 \text{lcp}(i-1,sa_{rk_{i-1}-1})\geqslant 1 lcp(i1,sarki11)1.

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

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

又因为后缀 s a r k i − 1 sa_{rk_{i}-1} sarki1 是最大的小于后缀 i i i 的后缀,所以 AB ⩽ s a r k i − 1 < AC = \texttt{AB}\leqslant sa_{rk_i-1} <\texttt{AC}= ABsarki1<AC= 后缀 i i i.

所以 lcp ( i , s a r k i − 1 ) ⊇ A \text{lcp}(i,sa_{rk_i-1})\supseteq\texttt{A} lcp(i,sarki1)A. 所以 h r k i ⩾ h r k i − 1 − 1 h_{rk_i}\geqslant h_{rk_{i-1}}-1 hrkihrki11.

那么有了这个引理,就可以求出 h e i g h t height 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;
}

k k k 最多减 n n n 次,所以最多加 2 n 2n 2n 次,均摊时间复杂度是 O ⁡ ( n ) \operatorname O(n) O(n).

求子串的最长公共前缀

引理二
KaTeX parse error: Expected 'EOF', got '&' at position 104: …j). \end{cases}&̲i\leqslant k\le…
证明:当 i = k i=k i=k k = j k=j k=j 时,引理成立。

lcp ( s a i , s a j ) = 0 \text{lcp}(sa_i,sa_j)=0 lcp(sai,saj)=0 时,引理成立。

否则,设
lcp ( s a i , s a k ) = A ,  lcp ( s a k , s a j ) = B ,  lcp ( s a i , s a j ) = C , ∣ A ∣ = a ,   ∣ B ∣ = b , ∣ C ∣ = c . \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. lcp(sai,sak)=A, lcp(sak,saj)=B, lcp(sai,saj)=C,A=a, B=b,C=c.

异德 c ⩾ min ⁡ ( a , b ) c\geqslant \min(a,b) cmin(a,b) A < B \texttt{A}<\texttt{B} A<B.

假设 c > a c>a c>a,如图所示,则 C ⊂ A \texttt{C}\subset \texttt{A} CA.

image-20230428184309603

容易看出 A-C ⩽ D ⩽ A-C \texttt{A-C}\leqslant \texttt{D}\leqslant \texttt{A-C} A-CDA-C,即 A = C \texttt{A}=\texttt{C} A=C,与 c > a c>a c>a 矛盾。

c > b c>b c>b 的情况同理。那么引理得证!

所以得到定理一
lcp ( s a i , s a j ) = min ⁡ ( lcp ( s a i , s a i + 1 ) , lcp ( s a i + 1 , s a j ) ) = min ⁡ ( lcp ( s a i , s a i + 1 ) , min ⁡ ( lcp ( s a i + 1 , s a i + 2 ) , lcp ( s a i + 2 , s a j ) ) ⋯ = min ⁡ k = i + 1 j h e i g h t k . \begin{align} \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{align} lcp(sai,saj)=min(lcp(sai,sai+1),lcp(sai+1,saj))=min(lcp(sai,sai+1),min(lcp(sai+1,sai+2),lcp(sai+2,saj))=k=i+1minjheightk.
那么就可以把求子串的 lcp \text{lcp} lcp 转化为 h e i g h t height height 数组的 RMQ 了。

简单应用

例题四:[GDOI 2015] 短信加密。

题目大意:给出一个字符串 s s s,对于 s s s k k k k ⩾ 2 k\geqslant 2 k2)个不重叠且相同子串 t t t,它的代价为 k ∣ t ∣ k|t| kt. 求最大的 k ∣ t ∣ k|t| kt 和相应的字典序最小的 t t t. n ⩽ 10000 n\leqslant 10000 n10000.

重复出现子串就相当于两个后缀的 lcp \text{lcp} lcp.

枚举 ∣ t ∣ = l |t|=l t=l. 把 h e i g h t height height 分块,满足每块内的 h e i g h t height height 值都 ⩾ l \geqslant l l,有些后缀不属于任何一个块。

根据 h e i g h t height height 的定义,块内的任意两个后缀的 lcp \text{lcp} lcp ⩾ l \geqslant l l,块外两个都 ⩽ l \leqslant l l.

那么我们贪心地求 t t t. 异德 t t t 越靠前, k k k 越大。所以将块内的 s a sa sa 排序,必须选第一个,能选的尽量选。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值