后缀数组 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} si∼i+2w−1 的排名。那么每次倍增合并后缀的排名,使用双关键字排序,最后得出的就是 r k rk rk 数组。
如图所示(《后缀数组——处理字符串的有力工具》,有改动)
那么内层排序我们使用基数排序和计数排序,就可以实现 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(∣ti∣logn),比 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,sai−1),即排名第 i i i 的后缀和排名第 i − 1 i-1 i−1 的后缀的最长公共前缀。
求 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.
hrki⩾hrki−1−1.
证明:当
h
r
k
i
−
1
⩽
1
h_{rk_{i-1}}\leqslant 1
hrki−1⩽1 时,引理成立。
当 h r k i − 1 ⩾ 1 h_{rk_{i-1}}\geqslant 1 hrki−1⩾1 时,即 lcp ( i − 1 , s a r k i − 1 − 1 ) ⩾ 1 \text{lcp}(i-1,sa_{rk_{i-1}-1})\geqslant 1 lcp(i−1,sarki−1−1)⩾1.
设后缀 i − 1 i-1 i−1 为 aAC \texttt{aAC} aAC,后缀 s a r k i − 1 − 1 sa_{rk_{i-1}-1} sarki−1−1 为 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} sarki−1 是最大的小于后缀 i i i 的后缀,所以 AB ⩽ s a r k i − 1 < AC = \texttt{AB}\leqslant sa_{rk_i-1} <\texttt{AC}= AB⩽sarki−1<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,sarki−1)⊇A. 所以 h r k i ⩾ h r k i − 1 − 1 h_{rk_i}\geqslant h_{rk_{i-1}}-1 hrki⩾hrki−1−1.
那么有了这个引理,就可以求出 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) c⩾min(a,b), A < B \texttt{A}<\texttt{B} A<B.
假设 c > a c>a c>a,如图所示,则 C ⊂ A \texttt{C}\subset \texttt{A} C⊂A.
容易看出 A-C ⩽ D ⩽ A-C \texttt{A-C}\leqslant \texttt{D}\leqslant \texttt{A-C} A-C⩽D⩽A-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 k⩾2)个不重叠且相同子串 t t t,它的代价为 k ∣ t ∣ k|t| k∣t∣. 求最大的 k ∣ t ∣ k|t| k∣t∣ 和相应的字典序最小的 t t t. n ⩽ 10000 n\leqslant 10000 n⩽10000.
重复出现子串就相当于两个后缀的 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 排序,必须选第一个,能选的尽量选。