AC自动机原理【学习笔记】
概述
A C AC AC自动机是以 T r i e Trie Trie为结构基础, k m p kmp kmp为思想基础建立的,主要用于多模式串匹配。
在 A C AC AC自动机上,所有的模式串构成一棵 T r i e Trie Trie树,而且利用 k m p kmp kmp的思想,在 T r i e Trie Trie上构造失配指针。
T r i e Trie Trie上的结点表示的是某个模式串的前缀,相当于一种状态,而 T r i e Trie Trie上的边就相当于是状态的转移。
f a i l fail fail指针
先把所有的模式串放到
T
r
i
e
Trie
Trie,举例如下:
假如说现在要匹配的文本串是
A
B
C
D
ABCD
ABCD,我们去树上匹配,会经过
2
,
3
,
4
2,3,4
2,3,4号节点匹配到模式串
A
B
C
ABC
ABC,然后就不能继续匹配。
如果接下来重新从根结点开始,复杂度会很高,我们可以借用 k m p kmp kmp的思想,跳到 7 7 7去, 7 7 7就是 4 4 4的失配指针。
More officially,
f
a
i
l
fail
fail指针指向 / 模式串的前缀中 / 匹配 / 当前状态的最长后缀。(断句要断好)
也就是说,
i
i
i的失配指针
j
j
j,满足
r
o
o
t
−
>
j
root->j
root−>j是
r
o
o
t
−
>
i
root->i
root−>i的一个后缀,而且是所有满足
r
o
o
t
−
>
i
=
r
o
o
t
−
>
j
x
root->i=root->j_x
root−>i=root−>jx中最大的那一个
j
x
j_x
jx。
9
9
9号点也满足条件,但是那里不是最长后缀,所以我们不跳到那里去。
下面是
f
a
i
l
fail
fail的求法:
设
T
r
i
e
Trie
Trie上当前的节点是
u
u
u,
u
u
u的父亲是
p
p
p,
t
r
i
e
[
p
]
[
c
]
=
u
trie[p][c]=u
trie[p][c]=u。
假设深度小于
u
u
u的所有结点的
f
a
i
l
fail
fail指针都已经求过。
1.
t
r
i
e
[
f
a
i
l
[
p
]
]
[
c
]
trie[fail[p]][c]
trie[fail[p]][c]存在,那么
f
a
i
l
[
u
]
=
t
r
i
e
[
f
a
i
l
[
p
]
]
[
c
]
fail[u]=trie[fail[p]][c]
fail[u]=trie[fail[p]][c]。
p
p
p的最长后缀的位置在
f
a
i
l
[
p
]
fail[p]
fail[p],在
f
a
i
l
[
p
]
fail[p]
fail[p]的位置再加一个字符
c
c
c就一定
u
u
u的最长后缀的位置,因为只在一个确定的串后面加上一个字符而已。
2.如果
t
r
i
e
[
f
a
i
l
[
p
]
]
[
c
]
trie[fail[p]][c]
trie[fail[p]][c]不存在,那么就要一直跳
f
a
i
l
fail
fail指针(反复横跳),找
t
r
i
e
[
f
a
i
l
[
f
a
i
l
[
p
]
]
]
[
c
]
trie[fail[fail[p]]][c]
trie[fail[fail[p]]][c],直到它存在,然后重复1.
3.如果真的不存在,那么把
f
a
i
l
fail
fail指向根。
具体可以用
b
f
s
bfs
bfs实现(有假设深度小于
u
u
u的所有结点的
f
a
i
l
fail
fail指针都已经求过) ,不过实现和刚才的思考过程是反的。
t
r
[
u
]
[
c
]
tr[u][c]
tr[u][c]可以理解为字典树上的一条边,也可以理解为一种状态转移,表示
u
u
u加上一个字符
c
c
c达到的状态。
代码实际上修改了
T
r
i
e
Trie
Trie的结构,但是使得匹配转移更加完善。它将 fail 指针跳转的路径做了压缩(就像并查集的路径压缩),使得本来需要跳很多次
a
i
l
ail
ail针变成跳一次。
匹配函数
f
a
i
l
fail
fail是最难的部分,
f
a
i
l
fail
fail理解之后求答案就水到渠成了吧。
时间复杂度
【参考:OIwiki】