详解传送门
后缀三姐妹之一,是解决字符串问题的一大有力工具。详细介绍请看上面的传送门。
这个自动机上的节点是固定的。每个节点包含
e
n
d
p
o
s
endpos
endpos相同的一些串。
不过有两种连边的方式。
首先是一个有向无环图。该图中每条边上对应一个字母。对于一个节点 i i i,从根到 i i i有一条路径,路上的字符构成一个字符串。这表示 i i i节点包含该字符串。一个节点可能包含多个字符串。类似于一个字典树。(下左)
其二是一颗树。每个节点的
e
n
d
p
o
s
endpos
endpos集合是相同的。每个节点由最长的那个字符串作代表。(下右)
这个树有很多优美的性质,随便列几条把:
①一个节点到根的路径上的所有节点 包含的字符串 为该节点包含的最长串的所有后缀。以"abcbc"节点为例,该节点包含:“abcbc”,“bcbc”,“cbc”(endpos集合为{5}),它的父亲是"bc"节点,该节点包含:“bc”,“c”(endpos集合为{3,5}),“bc”节点的父亲为根节点。仔细看看,这些串是不是"abcbc"的所有后缀?
②一个节点
P
P
P包含的串的个数为
l
e
n
(
P
)
−
l
e
n
(
l
i
n
k
(
P
)
)
len(P)-len(link(P))
len(P)−len(link(P))。这里
l
i
n
k
(
P
)
link(P)
link(P)为
P
P
P的父亲。
③一个节点的
e
n
d
p
o
s
endpos
endpos集合为它所有儿子的
e
n
d
p
o
s
endpos
endpos集合的交集。换句话说,一个节点的
e
n
d
p
o
s
endpos
endpos集合的大小为它所有儿子的
e
n
d
p
o
s
endpos
endpos集合大小的和。
④节点数不超过
2
n
−
1
2n-1
2n−1
⑤转移数量不超过
3
n
−
4
3n-4
3n−4
⑥一个字符串建出后缀自动机,它的
p
a
r
e
n
t
parent
parent树就是它反串的后缀树。这大概是很重要的一个性质。我们建立完自动机后,可以发现,非克隆点到根节点代表着它反串的一个后缀。而克隆点相当于是内部节点,它到根节点代表的是反串的某些后缀的最长公共前缀。凭借这一点可以做很多事情。
例串:“abcbc”
按照如上所述的构图方式,也就不难理解这个图了吧。
构图采用的是增量法。考虑每加一个点对原图的影响。
为什么要增加一个克隆点?
因为在那个情况下,
q
q
q节点里面包含的部分串的
e
n
d
p
o
s
endpos
endpos集合变了——
e
n
d
p
o
s
endpos
endpos集合增加了一个元素,即最后一个位置。
当
l
e
n
(
p
)
+
1
=
=
l
e
n
(
q
)
len(p)+1==len(q)
len(p)+1==len(q),说明
q
q
q中只有一个元素,也就不存在这个问题了。
而当
l
e
n
(
p
)
+
1
<
l
e
n
(
q
)
len(p)+1<len(q)
len(p)+1<len(q),那么
q
q
q中所含元素的
e
n
d
p
o
s
endpos
endpos集合就会分成两派,这是不允许的。于是我们需要调整这个东西:
(以下内容中,“节点”所指字符串均为对应节点最长串)
从
l
a
s
t
last
last节点往上走,走到
p
p
p节点发现
p
p
p节点中的元素 在后面接一个’c’可以到达
q
q
q节点。由于
l
e
n
(
p
)
+
1
<
l
e
n
(
q
)
len(p)+1<len(q)
len(p)+1<len(q),所以
q
q
q节点中不止一个元素。
由于
p
p
p是
l
a
s
t
last
last的一个后缀(因为是从
l
a
s
t
last
last往上走到
p
p
p),那么
p
+
p+
p+’c’一定在
l
a
s
t
last
last中出现过(在有向无环图上从根走到
p
p
p,再从
p
p
p走’c’边就有了
p
+
p+
p+’c’)。
而现在往
l
a
s
t
last
last后面加一个’c’,
p
+
p+
p+'c’就有了新的
e
n
d
p
o
s
endpos
endpos:最后一个位置。(注意到
p
p
p是
l
a
s
t
last
last的一个后缀)但是在
q
q
q节点中比
p
+
p+
p+’c’长的那些串并没有增加
e
n
d
p
o
s
endpos
endpos(因为
p
p
p是我们从
l
a
s
t
last
last往上走所遇到的第一个有’c’出边的节点,所以
p
p
p是结尾为’c’的最长后缀)
那么就要把这两部分分开。
分开的时候,从
p
p
p到根的路径上的节点如果有通过’c’连向
q
q
q,它们的
e
n
d
p
o
s
endpos
endpos也是会增加的(它们是
p
p
p的后缀)。把他们的’c’出边连向克隆点就行了。代码也大概就这样吧。
inline void build(int c){
int cur=++sz,p=last;
st[cur].len=st[last].len+1,st[cur].right=1;
for(;p!=-1&&!st[p].nxt[c];p=st[p].link)
st[p].nxt[c]=cur;
if(p==-1) st[cur].link=0;
else{
int q=st[p].nxt[c];
if(st[p].len+1==st[q].len) st[cur].link=q;
else{
int clone=++sz;st[clone]=st[q];
st[clone].right=0,st[clone].len=st[p].len+1;
for(;p!=-1&&st[p].nxt[c]==q;p=st[p].link)
st[p].nxt[c]=clone;
st[cur].link=st[q].link=clone;
}
}last=cur;
}