前言
这是一个咕了将近半年的文章,并不需要什么前置知识,只需要看的懂一些数学表达式就好了。
符号约定
- ∣ S ∣ |S| ∣S∣表示字符串 S S S的长度。
- S [ l . . . r ] S[l...r] S[l...r]表示由第 l l l个到 r r r个字符组成的 S S S的子串,位置由 1 1 1开始。
- S u f ( S ) \mathrm{Suf}(S) Suf(S)表示字符串 S S S的所有后缀构成的集合, S u f ′ ( S ) \mathrm{Suf'}(S) Suf′(S)表示除去自身的所有后缀构成的集合, S u f ( S , l ) \mathrm{Suf}(S,l) Suf(S,l)表示 S S S长度为 l l l的后缀。
- P r e ( S ) , P r e ′ ( S ) , P r e ( S , l ) \mathrm{Pre}(S),\mathrm{Pre'}(S), \mathrm{Pre}(S, l) Pre(S),Pre′(S),Pre(S,l)同理。
讲解
B o r d e r \mathrm{Border} Border
用
B
o
r
d
e
r
(
S
)
\mathrm{Border}(S)
Border(S)表示字符串
S
S
S相同前缀后缀构成的集合(这里用他们的长度记录)。用形式化的表示就是:
B
o
r
d
e
r
(
S
)
=
{
l
∣
0
≤
l
<
∣
S
∣
,
P
r
e
(
S
,
l
)
=
S
u
f
(
S
,
l
)
}
\mathrm{Border}(S)=\{l|0 \leq l < |S|, \mathrm{Pre}(S, l)=\mathrm{Suf}(S, l)\}
Border(S)={l∣0≤l<∣S∣,Pre(S,l)=Suf(S,l)}
对于
l
∈
B
o
r
d
e
r
(
S
)
l\in \mathrm{Border}(S)
l∈Border(S),我们称
P
r
e
(
S
,
l
)
\mathrm{Pre}(S,l)
Pre(S,l)或
S
u
f
(
S
,
l
)
\mathrm{Suf}(S,l)
Suf(S,l)为
S
S
S的
B
o
r
d
e
r
\mathrm{Border}
Border,可以发现,空串是所有串的
B
o
r
d
e
r
\mathrm{Border}
Border。
可以发现一个简单的性质:
∀
l
∈
B
o
r
d
e
r
(
S
)
,
B
o
r
d
e
r
(
P
r
e
(
S
,
l
)
)
=
B
o
r
d
e
r
(
S
u
f
(
S
,
l
)
)
⊂
B
o
r
d
e
r
(
S
)
\forall~~l \in \mathrm{Border}(S), \mathrm{Border}(\mathrm{Pre}(S,l))=\mathrm{Border}(\mathrm{Suf}(S,l))\sub \mathrm{Border}(S)
∀ l∈Border(S),Border(Pre(S,l))=Border(Suf(S,l))⊂Border(S)
这个比较显然,用通俗语言概括就是我的
B
o
r
d
e
r
\mathrm{Border}
Border的
B
o
r
d
e
r
\mathrm{Border}
Border是我的
B
o
r
d
e
r
\mathrm{Border}
Border。
用
f
a
i
l
(
S
)
\mathrm{fail}(S)
fail(S)表示最长
B
o
r
d
e
r
\mathrm{Border}
Border,形式化就是(很蠢):
f
a
i
l
(
S
)
=
P
r
e
(
S
,
max
l
∈
B
o
r
d
e
r
(
S
)
{
l
}
)
\mathrm{fail}(S)=\mathrm{Pre} \left( S, \max_{l\in \mathrm{Border}(S)} \{l\} \right)
fail(S)=Pre(S,l∈Border(S)max{l})
接着找一下规律:
∣
f
a
i
l
(
S
)
∣
∈
B
o
r
d
e
r
(
S
)
∣
f
a
i
l
(
f
a
i
l
(
S
)
)
∣
∈
B
o
r
d
e
r
(
S
)
∣
f
a
i
l
(
f
a
i
l
(
f
a
i
l
(
S
)
)
)
∣
∈
B
o
r
d
e
r
(
S
)
.
.
.
\begin{aligned} \left| \mathrm{fail}(S) \right| & \in \mathrm{Border}(S)\\ \left| \mathrm{fail}(\mathrm{fail}(S)) \right| & \in \mathrm{Border}(S)\\ \left| \mathrm{fail}(\mathrm{fail}(\mathrm{fail}(S))) \right| & \in \mathrm{Border}(S)\\ ... \end{aligned}
∣fail(S)∣∣fail(fail(S))∣∣fail(fail(fail(S)))∣...∈Border(S)∈Border(S)∈Border(S)
那么可以发现,存在
∣
f
a
i
l
i
(
S
)
∣
∈
B
o
r
d
e
r
(
S
)
\left|\mathrm{fail}^i(S)\right|\in\mathrm{Border}(S)
∣∣faili(S)∣∣∈Border(S)
那么我们能否通过
f
a
i
l
(
S
)
\mathrm{fail}(S)
fail(S)找到所有的
B
o
r
d
e
r
\mathrm{Border}
Border呢,答案是可行的,至于证明,用反证法搞一搞就好了,没什么难的。
求解 f a i l \mathrm{fail} fail
容易想到 O ( n 2 ) O(n^2) O(n2)的做法,由于太简单就不贴出来了,因为我们需要 O ( n ) O(n) O(n)的。
首先可以发现,假设我们求出了字符串 S S S中长度为 1... n 1...n 1...n的所有前缀的 f a i l ( P r e ( S , i ) ) \mathrm{fail}(\mathrm{Pre}(S,i)) fail(Pre(S,i)),那么很明显, f a i l ( P r e ( S , i + 1 ) ) \mathrm{fail}(\mathrm{Pre}(S,i+1)) fail(Pre(S,i+1))最多只会增加 1 1 1,否则就是不断缩小了,而具体怎么缩小,本来需要查看 B o r d e r ( S i ) \mathrm{Border}(S_i) Border(Si)中所有的值,看看是否有能够拓展的,但是按照我们先前得到的性质,只需要往回找到一个 f a i l ( P r e ( S , j ) ) \mathrm{fail}(\mathrm{Pre}(S,j)) fail(Pre(S,j))(设为 t t t),满足 S t + 1 = S i + 1 S_{t+1}=S_{i+1} St+1=Si+1或者 j = 0 j=0 j=0即可。
容易想到代码实现(这里的
S
S
S变成了
T
T
T):
n = strlen(T + 1), fail[1] = 0;
for (int i = 2, j = 0; i <= n; i++) {
while(j && T[i] != T[j + 1]) j = fail[j];
if (T[i] == T[j + 1]) j++;
fail[i] = j;
}
求匹配
现在我们对模式串 T T T求出了 f a i l \mathrm{fail} fail,现在要用 T T T来匹配 S S S,那么我们可以使用类似的代码进行匹配:
m = strlen(S + 1);
for (int i = 1, j = 0; i <= m; i++) {
while(j && (S[i] != T[j + 1] || j == n)) j = fail[j];
if (S[i] == T[j + 1]) j++;
f[i] = j; // 如果f[i]==n 则表示S[i-n+1...i]=T
}
f a i l T r e e \mathrm{fail~Tree} fail Tree
对于一个字符串
S
S
S的所有前缀,我们都计算出了他们的
f
a
i
l
\mathrm{fail}
fail。现在我们转换一下定义
f
a
i
l
(
i
)
\mathrm{fail}(i)
fail(i)表示
P
r
e
(
S
,
i
)
\mathrm{Pre}(S,i)
Pre(S,i)的最长
B
o
r
d
e
r
\mathrm{Border}
Border。容易想到构建一颗
f
a
i
l
\mathrm{fail}
fail树,其中儿子指向父亲的边可以表示为
i
⟶
f
a
i
l
(
i
)
i\longrightarrow \mathrm{fail}(i)
i⟶fail(i),显然根据这个定义,一个点到跟的路径上的所有点(不包括自己)都是这个点所代表的前缀的
B
o
r
d
e
r
\mathrm{Border}
Border,且他们的长度随着深度的减小而减小。那么可以发现,在这颗树上,两个点的LCA
就是这两个点所代表的前缀的最长公共
B
o
r
d
e
r
\mathrm{Border}
Border。
例题
Power Strings
求一个字符串由多少个重复的子串连接而成,也就是求循环节的循环次数。
例如 ababab
由三个 ab
连接而成,abcd
由一个 abcd
连接而成。
容易想到使用
f
a
i
l
\mathrm{fail}
fail的奇怪性质,假设
f
a
i
l
(
n
)
≤
⌊
n
2
⌋
\mathrm{fail}(n)\leq \lfloor\frac{n}{2}\rfloor
fail(n)≤⌊2n⌋,那么我们画一个图:
因为
B
o
r
d
e
r
\mathrm{Border}
Border的性质,可以得到:
就这样不停地用 P r e ( S , n − f a i l ( n ) ) \mathrm{Pre}(S,n-\mathrm{fail}(n)) Pre(S,n−fail(n))填充 S S S,假如剩下了一节长度小于 n − f a i l ( n ) n-\mathrm{fail}(n) n−fail(n)的,那么就一定无解。
若恰好可以填满,那么我们可以证明, P r e ( S , n − f a i l ( n ) ) \mathrm{Pre}(S,n-\mathrm{fail}(n)) Pre(S,n−fail(n))一定是 S S S最小的循环节。
那么差不多可以得到问题的答案啦:
a
n
s
=
{
n
n
−
f
a
i
l
(
n
)
(
n
−
f
a
i
l
(
n
)
)
∣
n
1
o
t
h
e
r
w
i
s
e
ans=\begin{cases} \cfrac{n}{n-\mathrm{fail}(n)} & (n-\mathrm{fail}(n))|n\\ 1 & otherwise \end{cases}
ans=⎩⎨⎧n−fail(n)n1(n−fail(n))∣notherwise
[NOI2014]动物园
给定 N N N个字符串,对于每一个字符串,算出它每一个前缀 P r e ( S , i ) \mathrm{Pre}(S,i) Pre(S,i)不超过该前缀一半的 B o r d e r \mathrm{Border} Border的数量,称为 n u m ( i ) \mathrm{num}(i) num(i)。你需要将每一个前缀的 n u m ( i ) + 1 \mathrm{num}(i)+1 num(i)+1乘起来,作为字符串 S S S的答案。答案对 1 0 9 + 7 10^9+7 109+7取模。
我们只需要将 n u m ( i ) \mathrm{num}(i) num(i)都求出来就好了。
很显然, n u m ( i ) \mathrm{num}(i) num(i)一定是 f a i l ( i ) \mathrm{fail}(i) fail(i)在 f a i l T r e e \mathrm{fail~Tree} fail Tree上的一个祖先,那么最暴力的方法就是对于每一个前缀,暴力地找符合条件的点。
但是数组开不下,而且很容易 T L E TLE TLE,那么就需要 O ( n ) O(n) O(n)的方法了。
首先在求 f a i l \mathrm{fail} fail的时候,顺道求出 i i i在 f a i l T r e e \mathrm{fail~Tree} fail Tree上到根路径上的节点个数(包括 i i i自身),这里设为 c n t i cnt_i cnti。那么显然, n u m ( i ) \mathrm{num}(i) num(i)就是到根路径上的某个 c n t j cnt_j cntj。接着我们可以发现, n u m ( i ) \mathrm{num}(i) num(i)每右移一位的时候,至多增加 1 1 1,也就是说,我们可以使用类似匹配文本串的方式,加一个小判断,进而求出 j j j。
for (int i = 2, j = 0; i <= n; i++) {
while (j && str[i] != str[j+1]) j = fail[j];
if (str[i] == str[j+1]) j++;
if ((j<<1) > i) j = fail[j]; // j至多增加1,也就是说至多跳到i/2+1
ans = ans * (cnt[j] + 1) % MOD;
}