后缀自动机
在后缀自动机之前还需要一些前置知识。
End pos 集合
即结束的位置集合,简称 E p Ep Ep 集合,代表一个子串在原串中出现的右端点。例如在下面这个字符串:
ZHOUSHENNISHISHENMEJUESHIDABAOBEI
这些加粗的
S
H
SH
SH的右端点的位置分别是
6
,
12
,
15
,
24
6,12,15,24
6,12,15,24,那么在这个字符串里,
S
H
SH
SH的
E
p
Ep
Ep 集合就是
{
6
,
12
,
15
,
24
}
\{6,12,15,24\}
{6,12,15,24}。
E
p
Ep
Ep 相同的子串叫一个等价类。
llen 和 lstr
l
l
e
n
llen
llen表示
l
o
n
g
e
s
t
l
e
n
t
h
longest lenth
longestlenth,
l
s
t
r
lstr
lstr表示
l
o
n
g
e
s
t
s
t
r
i
n
g
longest string
longeststring。
对一个
E
p
Ep
Ep 等价类,满足这个的等价类最长子串就是这个
E
p
Ep
Ep 等价类的
l
s
t
lst
lstr,
l
s
t
r
lstr
lstr 的长度就是
l
l
e
n
llen
llen。
例如,对于前面那个字符串,
E
p
Ep
Ep 集合为
{
6
,
12
,
15
,
24
}
\{6,12,15,24\}
{6,12,15,24}的
l
s
t
r
lstr
lstr就是
S
H
SH
SH,
l
l
e
n
llen
llen就是
8
8
8。
后缀自动机
以字符串 a a b b a b d aabbabd aabbabd 举例。
将
a
a
b
b
a
b
d
aabbabd
aabbabd 的所有后缀建树。
再将没有分支的链缩成点:
再将每个节点指向它的
f
a
i
l
fail
fail(
f
a
i
l
[
x
]
fail[x]
fail[x] 为
x
x
x 在后缀树上的父亲):
构建后缀自动机伪代码如下:
int sam_add(char c){
v=上次加入的点的编号;
u=++tot;
u为加入的点
v一直连边,直到遇到有一条字符为c的出边的点x
y=x的出边
if(!x) return fa[u]=1;
if(len[x]+1==len[y]) return fa[u]=y;
else{
新建点p
llen[p]=llen[x]+1;
fa[p]=fa[y];
fa[u]=fa[y]=p
设好p的出边
找到p的入边
}
}
基本应用
- 求一个字符串有多少个本质不同的子串。
- 求一个字符串有多少个包含某个字符串 T T T的本质不同的子串。
- 求每个前缀/后缀的本质不同子串个数
- 求两个串 S , T S,T S,T 的最长公共子串
- 求 L C P ( S [ i , n ] , S [ j , n ] ) LCP(S[i,n], S[j,n]) LCP(S[i,n],S[j,n])
- 求第 k k k 小子串的值(本质不同/本质相同)
- 求一个子串的出现次数
- 不重叠最长重复子串
例题
基础题
- 给定 S , T S,T S,T,求他们有几对子串相同。
- 给 n n n 个字符串,求有多少个子串是其中至少 k k k 个字符串的子串。
- 求 ∑ ( l c p ( i , j ) ) \sum(lcp(i,j)) ∑(lcp(i,j))。
- 求长度为 k k k 的字典序最小的子串。
- 给出字符串 s s s,数组 a a a 和整数 k k k,你需要选出两个不同的后缀 i i i 和 j j j 使得它们的 l c p lcp lcp 长度大于等于 k k k 同时最大化 a i ∗ a j a_i*a_j ai∗aj。
Fim1
题目简述:给定一个字符串
S
S
S,维护一个字符串序列
T
T
T。其中,
T
.
a
p
p
e
n
d
(
T
[
x
]
+
c
)
;
T
.
a
p
p
e
n
d
(
c
+
T
[
x
]
)
T.append(T[x]+c);T.append(c+T[x])
T.append(T[x]+c);T.append(c+T[x])。
询问T[x]在S[l,r]中出现的次数。
[BZOJ4556]字符串
题目简述:给定一个字符串,有 Q Q Q 次询问,每次给出 [ a , b ] [ c , d ] [a,b][c,d] [a,b][c,d],询问 s [ a , b ] s[a,b] s[a,b] 的所以子串 s [ c , d ] s[c,d] s[c,d] 的最长公共前缀的最大值。
[CF700E]Cool Slogans
题目传送门
题目简述:给定一个字符串
S
S
S,要求构造字符串序列
s
1
,
s
2
,
.
.
.
,
s
k
s_1,s_2,...,s_k
s1,s2,...,sk,满足任意
s
i
s_i
si 都是
S
S
S 的子串,且任意
i
∈
[
2
,
n
]
i\in[2,n]
i∈[2,n],都有
s
i
−
1
s_{i-1}
si−1 在
s
i
s_i
si 中出现了至少
2
2
2 次(可以有重叠部分,只要起始、结尾位置不同即可)。 求可能的最大的
k
k
k 的值(即序列的最大可能长度)。
NSUBSTR-Substrings
题目传送门
题目简述:给出一个字符串,对于每个
k
k
k,求出所有长度为
k
k
k 的子串中出现次数的最大值。
[TJOI2016]字符串
题目传送门
题目简述:给出一个字符串,有
m
m
m 次询问,每次询问子串
[
a
,
b
]
[a, b]
[a,b] 的所有子串中与子串
[
c
,
d
]
[c, d]
[c,d] 的
L
C
P
LCP
LCP 最大值是多少。
广义后缀自动机
即根据多个串建自动机。
其中
a
d
d
add
add 部分与一般的后缀自动机一样。
用所有模式串建出一颗
t
r
i
e
trie
trie 树,对其进行
d
f
s
/
b
f
s
dfs/bfs
dfs/bfs 遍历构建后缀自动机,在
i
n
s
e
r
t
insert
insert 时使
l
a
s
t
last
last 为它在
t
r
i
e
trie
trie 树上的父亲,其余和普通后缀自动机一样。
d f s dfs dfs 代码如下:
void dfs(int x){
for(int i=0;i<26;i++){
int y=tr[x][i];
if(y) pos[y]=insert(c[y],pos[x]),dfs(y);
}
}
void build(){pos[1]=1,dfs(1);}
b f s bfs bfs 代码如下:
void build(){
for(int i=0;i<C;i++)
if(tr[1][i]) q.push(tr[1][i]);
pos[1]=1;
while(!q.empty()){
int x=q.front();q.pop();
pos[x]=insert(c[x],pos[fa[x]]);
for(int i=0;i<C;i++)
if(tr[x][i]) q.push(tr[x][i]);
}
}
例题
Codechef TSUBSTR
题目简述:一棵trie,考虑所有从上往下的串。多组询问,每次询问给你一个字典序,问你字典序第k大的串。
[ZJOI2015]幻想乡
题目传送门
题目简述:给一个叶子数不超过
20
20
20 的
t
r
i
e
trie
trie,求有多少个不同的子串。
[BZOJ4545]DQS的Trie
题目简述:一颗
t
r
i
e
trie
trie 树,
q
q
q 次操作,操作有
3
3
3 种:
1.求这棵树上本质不同的子串数量。
2.插入一个子树,保证总大小不超过
100000
100000
100000。
3.询问一个字符串在
t
r
i
e
trie
trie 树上出现过多少次,保证所有询问串总长度不超过
100000
100000
100000。
[NOI2018]你的名字
题目传送门
题目简述:给一个
S
S
S,每次问
T
T
T有多少个本质不同的子串不是
S
[
l
…
r
]
S[l…r]
S[l…r]的子串。
无题
题目简述:有一颗树,节点上带字母。每次给出一个串和一条路径,问这个串在这条路径上出现了几次。