符号
- 子串:字符串中连续的一段。
- p r e ( s , x ) pre(s, x) pre(s,x): s [ 1... x ] s[1 ... x] s[1...x] 组成的子串。
- s u f ( s , x ) suf(s, x) suf(s,x): s [ ∣ s ∣ − x . . . ∣ s ∣ ] s[|s| - x ... |s|] s[∣s∣−x...∣s∣] 组成的子串。
- 周期:若
p
p
p 为字符串
s
s
s 的周期,则
p
p
p 满足
- 0 < p < ∣ s ∣ 0 < p < |s| 0<p<∣s∣.
- s [ i ] = s [ i + p ] , i ∈ { 1 , 2 , 3... , ∣ s ∣ − p } s[i] = s[i + p], i \in \left\{1, 2, 3..., |s| - p \right\} s[i]=s[i+p],i∈{1,2,3...,∣s∣−p}.
-
b
o
r
d
e
r
border
border: 若
p
r
e
(
s
,
r
)
pre(s, r)
pre(s,r) 称为字符串
s
s
s 的
b
o
r
d
e
r
border
border,则
r
r
r 满足
- 0 < r < ∣ s ∣ 0 < r < |s| 0<r<∣s∣.
- p r e ( s , r ) = s u f ( s , r ) pre(s, r) = suf(s, r) pre(s,r)=suf(s,r)
p e r i o d period period 与 b o r d e r border border
-
3
3
3 和
6
6
6 都是
a
b
c
a
b
c
a
b
abcabcab
abcabcab 的周期。
- 注意,一个周期不需要恰好能整除串长。
- a b c a b abcab abcab 和 a b ab ab 都是 a b c a b c a b abcabcab abcabcab 的 b o r d e r border border。
-
p
r
e
(
s
,
k
)
pre(s, k)
pre(s,k) 是
s
s
s 的
b
o
r
d
e
r
border
border
⇔
\Leftrightarrow
⇔
∣
s
∣
−
k
|s| - k
∣s∣−k 是
s
s
s 的周期。
- 画图,自证不难.
b o r d e r border border 的性质
- (传递性1)若 S S S 是 T T T 的 b o r d e r border border, T T T 是 R R R 的 b o r d e r border border, 则 S S S 是 R R R 的 b o r d e r border border.
- (传递性2)若 S S S 是 R R R 的 b o r d e r border border, T T T 是 R R R 的 b o r d e r border border, 且 ∣ S ∣ < ∣ T ∣ |S| < |T| ∣S∣<∣T∣,则 S S S 是 T T T 的 b o r d e r border border.
- 以上两点画图显然.
- (封闭性)记
m
b
(
S
)
mb(S)
mb(S) 为
S
S
S 的最长
b
o
r
d
e
r
border
border,则
m
b
(
m
b
(
S
)
)
,
m
b
(
m
b
(
m
b
(
S
)
)
)
.
.
.
mb(mb(S)), mb(mb(mb(S)))...
mb(mb(S)),mb(mb(mb(S)))... 为
S
S
S 的所有
b
o
r
d
e
r
border
border.
- 由 传递性1、2 得.
KMP模式匹配
- 在主串
S
S
S 中查找 模式串
T
T
T 第一次出现的位置。
- 暴力匹配 O ( ∣ S ∣ ⋅ ∣ T ∣ ) O(|S|\cdot|T|) O(∣S∣⋅∣T∣),
- 考虑 S S S 以 i i i 结尾,长度为 j j j 的子串 S [ i − j . . . i ] S[i - j...i] S[i−j...i],发现,字符串的任意子串总是该字符串的一个前缀的后缀,即 S [ i − j . . . i ] = s u f ( p r e ( S , i ) , j ) S[i - j...i] = suf(pre(S, i), j) S[i−j...i]=suf(pre(S,i),j).
- 观察暴力匹配的过程,我们用两个指针
i
,
j
i, j
i,j,分别对应在
S
,
T
S, T
S,T 中扫描。
- 每次扫描开始时,都应有 S [ i . . . i + j ] = s u f ( p r e ( S , i + j ) , j ) = p r e ( T , j ) S[i...i + j] = suf(pre(S, i + j), j) = pre(T, j) S[i...i+j]=suf(pre(S,i+j),j)=pre(T,j)
- 若 S [ i + j + 1 ] = T [ j + 1 ] S[i + j + 1] = T[j + 1] S[i+j+1]=T[j+1],则将 j j j 往后移一位.
- 若 S [ i + j + 1 ] ≠ T [ j + 1 ] S[i + j + 1] \neq T[j + 1] S[i+j+1]=T[j+1],则将 i i i 往后移一位, j j j 从头开始扫描.
- 复杂度为 O ( ∣ S ∣ ⋅ ∣ T ∣ ) O(|S|\cdot|T|) O(∣S∣⋅∣T∣).
- 暴力慢在哪呢?慢在 i , j i, j i,j 往后移的步骤上。如果我们能够跳过一些肯定匹配不上的位置,或许能更快!
- 这时,K、M、P 三人提出了一个KMP策略 :
- 我们用两个指针 i , j i, j i,j 分别表示, S [ i − j + 1... i ] S[i - j + 1...i] S[i−j+1...i] 与 T [ 1... j ] T[1...j] T[1...j] 完全相等,即 S S S 匹配到 i i i, T T T 匹配到 j j j.
- 首先介绍 KMP性质: 每次扫描开始时,都应有 s u f ( p r e ( S , i ) , j ) = p r e ( T , j ) suf(pre(S, i), j) = pre(T, j) suf(pre(S,i),j)=pre(T,j), 且 j j j 越大越好.
- 若 S [ i + 1 ] = T [ j + 1 ] S[i + 1] = T[j + 1] S[i+1]=T[j+1],则将 i i i, j j j 各往后移一位,满足KMP性质.
- 若
S
[
i
+
1
]
≠
T
[
j
+
1
]
S[i + 1] \neq T[j + 1]
S[i+1]=T[j+1],也就是失配了,我们该怎么调整指针来避免每次都重新开始匹配呢?
- 由 KMP性质,我们知道此时的 i , j i, j i,j 肯定满足 s u f ( p r e ( S , i ) , j ) = p r e ( T , j ) suf(pre(S, i), j) = pre(T, j) suf(pre(S,i),j)=pre(T,j),因而它们的 b o r d e r border border 也相同.
- 于是,我们每次将 j j j 跳跃到 p r e ( T , j ) pre(T, j) pre(T,j) 当前最长的 b o r d e r border border 上,不难发现若能匹配下去,这个最长的 b o r d e r border border 一定能匹配得最长,且满足 KMP性质.
- 如果还是不能匹配,接着跳 b o r d e r border border,直到 j = − 1 j = -1 j=−1.
- 结合封闭性,我们只需求出 T T T 每个前缀的最长 b o r d e r border border 就可以快速进行上述步骤.
- 记 p r e ( T , j ) pre(T, j) pre(T,j) 的最长 b o r d e r border border 为 n e x t [ j ] next[j] next[j],以下是利用 n e x t next next 数组匹配的代码。
int kmp(string s, string t) {
int n = s.size(), m = t.size();
int j = -1;
for (int i = 0; i < n; i++) {
while (j >= 0 && s[i] != t[j + 1]) j = next[j];
if (s[i] == t[j + 1]) j++;
if (j + 1 == m) { //匹配成功
return i - m + 1;
}
}
return -1; //匹配失败
}
- j j j 后移的次数最多为 ∣ T ∣ |T| ∣T∣, j j j 回退的次数总是少于后移的次数,因而匹配复杂度摊还 O ( ∣ S ∣ ) O(|S|) O(∣S∣).
- 把子串看作一个前缀的真后缀,不难发现求解 n e x t next next 的方法与匹配类似,以下是求解 n e x t next next 的代码.
void init(string t) {
int m = t.size();
next[0] = -1;
int j = -1;
for (int i = 1; i < m; i++) {
while (j >= 0 && t[i] != t[j + 1]) j = next[j];
if (t[i] == t[j + 1]) j++;
next[i] = j;
}
}
-
预处理 n e x t next next 的复杂度摊还 O ( ∣ T ∣ ) O(|T|) O(∣T∣),因此总复杂度 O ( ∣ S ∣ + ∣ T ∣ ) O(|S|+|T|) O(∣S∣+∣T∣).
-
注意:我习惯以 0 0 0 为字符串下标的起点,因此 n e x t next next 的初值赋为 − 1 -1 −1,在调用 b o r d e r border border 长度时应加上 1 1 1。
-
虽然蛮讨厌的,但在构造失配树的时候下标最好还是从 1 1 1 开始。
失配树
- 定义:将 n e x t [ i ] next[i] next[i] 看作 i i i 的父节点,那么通过 n e x t next next 数组可以把 0 ∼ N 0\sim N 0∼N 点连成一棵树。
- 性质:
- 点 i i i 的所有祖先都是 p r e ( s , i ) pre(s,i) pre(s,i) 的 b o r d e r border border.
- 没有祖先关系的两个点 i , j i, j i,j 没有 b o r d e r border border 关系.
- 任意两点的 L C A LCA LCA 为它们的最长公共 b o r d e r border border.
- 结合失配树,计算
n
e
x
t
[
i
]
next[i]
next[i] 的过程可以看作:从
j
=
f
a
[
i
−
1
]
j = fa[i - 1]
j=fa[i−1] 开始不断往上走,找第一个满足
s
[
j
+
1
]
=
s
[
i
]
s[j + 1] = s[i]
s[j+1]=s[i] 的点,把
i
i
i 的父亲设为
j
+
1
j + 1
j+1.
- 为什么是这样呢?
- 考虑失配树的定义, j = f a [ i − 1 ] ⇔ j = fa[i - 1] \Leftrightarrow j=fa[i−1]⇔ s [ 0... j ] s[0...j] s[0...j] 是 p r e ( s , i − 1 ) pre(s, i - 1) pre(s,i−1) 的最长 b o r d e r border border.
- 又因为 f a [ j ] fa[j] fa[j] 是 p r e ( s , j ) pre(s,j) pre(s,j) 的最长 b o r d e r border border,所以向上走寻找匹配的过程就是利用 KMP策略 求 n e x t [ i ] next[i] next[i] 的过程.
- 代码就不放了。
例题
- 【例1】P4391 [BOI2009]Radio Transmission 无线传输
- 题意简述:求一个串的最小周期。
- 由上文提到的 “ p e r i o d period period 与 b o r d e r border border 的关系”,我们知道 最小周期 为 串长减去最长的 b o r d e r border border.
- 回想 n e x t next next 数组的定义,此题得解.
- 【例2】P3435 [POI2006] OKR-Periods of Words
- 题意简述:求一个串所有前缀的最大周期。
- 最大周期 即为 串长减去最短的 b o r d e r border border,求出最短的 b o r d e r border border 就万事大吉了.
- 回想 失配树 的性质,最短的 b o r d e r border border 不就是该点到根的路径上离根最近的点吗?想到这里就有很多搞法了.
- 可以建出失配树,然后枚举失配树的每一个 儿子,从每一个儿子出发往下跑一遍 d f s dfs dfs,由于每个节点只会被遍历一次,故复杂度是 O ( n ) O(n) O(n) 的.
- 也可以利用并查集思想,把每一个与根节点直接相连的儿子作为代表元,路径压缩后即可快速查询.
- 【例3】POJ2185 Milking Grid
- 题意简述:求一个二维字符矩阵的最小周期。
- 行列分别跑一次 KMP,在跑列的 KMP 时,把每行看作一个整体,每次检验时暴力检验所有行。行方向 KMP 同理。复杂度 O ( R × M ) O(R \times M) O(R×M)
- 【例4】P2375 [NOI2014] 动物园
- 题意简述:求出一个串每一个前缀的 b o r d e r border border 中,长度不超过该前缀长度一半的 b o r d e r border border 的数量。
- 上失配树,每个节点 i i i 都倍增跳到第一个满足 j ≤ i / 2 j \leq i/2 j≤i/2 的点 j j j 上,然后统计。时间 O ( T n l o g n ) O(Tnlogn) O(Tnlogn),常数小的做法可以卡过.
- 【例5】P3426 [POI2005]SZA-Template
- 题意简述:求字符串最小的一个前缀,使得它拼接(可以重叠)若干次后恰好能还原字符串。
- 若 p r e ( s , i ) pre(s, i) pre(s,i) "恰好"能还原原串,说明该长度为 i i i 的前缀与长度为 i i i 的后缀是相同的,也就是说答案是一个 b o r d e r border border.
- 那只要是 b o r d e r border border,就一定能还原吗?不是,该 b o r d e r border border 在原串中匹配的位置之间间隔不能超过 b o r d e r border border 的长度.
- 于是就有了很多想法。
- 一种是上失配树,那么符合条件的 b o r d e r border border 一定是 ∣ s ∣ |s| ∣s∣ 的祖先节点.
- 枚举 ∣ s ∣ |s| ∣s∣ 的所有祖先,设当前在点 i i i,那么 i i i 的子树中相邻节点的距离不能超过 i i i,否则就会空出一段.
- 另一种想法是直接 d p dp dp,设 f i f_i fi 是覆盖 p r e ( s , i ) pre(s, i) pre(s,i) 的最小 b o r d e r border border,则 f i f_i fi 只有 2 2 2 种取值: i i i 或 f n e x t i f_{next_i} fnexti.
- 为什么是 f n e x t i f_{next_i} fnexti 呢?由上面的分析我们知道 f i ≤ n e x t i f_i \leq next_i fi≤nexti,凡是不能覆盖 n e x t i next_i nexti 的,一定覆盖不了 p r e ( s , i ) pre(s, i) pre(s,i),因此 f i f_i fi 只能由 f n e x t i f_{next_i} fnexti 转移过来.
- 那么什么时候 f i f_i fi 能从 f n e x t i f_{next_i} fnexti 转移呢?因为 b o r d e r border border 在原串中匹配的位置之间间隔不能超过 b o r d e r border border 的长度,因此,只有存在一个 j j j,满足 i − n e x t i ≤ j i - next_i \leq j i−nexti≤j 且 f j f_j fj 是由 f n e x t i f_{next_i} fnexti 转移来的时候, f i f_i fi 才能等于 f n e x t i f_{next_i} fnexti.
- 开个桶记录一下最后一个 f i = f n e x t i f_i = f_{next_i} fi=fnexti 的位置即可做到 O ( n ) O(n) O(n).