AC自动机用途
给多个字符串t
,再询问一个字符串s
,问有多少个字符串t
出现在询问的字符串s
中。
前置技能
学AC自动机之前,先学会什么是字典树
,什么是kmp
。kmp
我写过一篇博客,就不讲了,就是next
数组保存一个最长匹配前缀。字典树就更简单了,每个节点从根节点开始,出现一个字符就在父亲节点上连上下一个节点,也不多说。有需要再写一篇博客。
AC自动机
对于这个玩意,都说是字典树
上跑KMP
,到也没错。朴素KMP
是在一个串上面跑next
,而AC自动机
只是变成了在字典树节点上跑fail指针
,每个fail
指针保存是最长匹配后缀。推荐一名大佬博客,讲的挺不错的。
朴素的写法,每一个字符串t
对s
做一次kmp
算法或者 对所有的t
字符串建一颗字典树,然后每个位置匹配一下,很显然$O(n^2)$的复杂度,绝对超时。
仔细思考一下,字典树里面,每次匹配都要从下一个位置开始跑一次匹配,类比一下没有kmp
的朴素字符串匹配。是不是有点相似。普通单个字符串匹配,是不是枚举每一个位置,然后做一次暴力匹配?然后kmp
做了什么,通过next
找到最大匹配前缀。那么我们是否可以在字典树用fail
保存最大匹配后缀呢?显然是可以的,不然AC自动机
干啥.
举个栗子: 假设有三个t asa
aaa
aas
, 匹配aasab
字典树建成这个样子,丑了点,别在意,重在思想。
然后一开始吧aas
匹配掉了没啥意见吧,然后继续匹配aasa
,很显然这个时候没有这个字符串,那么肯定就要转移,
转移到哪去呢?最长匹配后缀啊。如图是不是这样。
然后匹配asa
,再继续匹配b
,很显然失配了啊,然后找最长匹配后缀,只有根节点了,然后根节点背后又没有b。所以啥都没了,匹配最长后缀为空串。
好了,现在差不多理解这个算法的思想,接下来就是一些细节,不知道我还有没有没有考虑到的,如果有请在评论区提问。
1.上诉所讲的例子很明显,aas
向as
跳转,如果后继没有a
这个节点怎么办?asa
改成asb
,asa
要匹配谁呢?这个在kmp
算法里面也有这个问题,很显然,继续找下去就能解决问题,假设 再加一个tsb
,aasb
适配,aas
调转到as
没有后继b
,再跳转到匹配后缀s
,有后继b
就匹配sb
,类推,如果还没有找到就继续想上找。ps: 这个地方可以优化,fail指针找到了最长匹配后缀,然后字典树的下一个节点可以直接跳到对应位置如例子 aas
后继a
可以直接跳到为asa
这个节点,不过不优化也没啥影响,因为字符串长度是固定的,最多不会跳超过n
次,不优化只是多了个常数
2.多个匹配怎么办?没到一个节点把他的fail
找下去记录个数,如图字典树匹配sa
,的时候会找他的fail
,找到a
然后把a
这个节点记录,同理找sab
,不仅仅把自己匹配了,还要把b
匹配掉
3.怎么记录个数,如果一个字符串多次出现怎么办,看代码,记录一下就行了。
这个AC自动机的思想还是要学好,后面回文自动机要用到这个玩意的思想。
1 | namespace Aho_Corasick_Automaton { |
再贴一个大哥板子1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39queue<int>q;
struct Aho_Corasick_Automaton {
int c[N][26],val[N],fail[N],cnt;
void ins(char *s) {
int len=strlen(s);
int now=0;
for(int i=0; i<len; i++) {
int v=s[i]-'a';
if(!c[now][v])
c[now][v]=++cnt;
now=c[now][v];
}
val[now]++;
}
void build() {
for(int i=0; i<26; i++)
if(c[0][i])
fail[c[0][i]]=0,q.push(c[0][i]);
while(!q.empty()) {
int u=q.front();
q.pop();
for(int i=0; i<26; i++)
if(c[u][i])
fail[c[u][i]]=c[fail[u]][i],q.push(c[u][i]);
else
c[u][i]=c[fail[u]][i];
}
}
int query(char *s) {
int len=strlen(s);
int now=0,ans=0;
for(int i=0; i<len; i++) {
now=c[now][s[i]-'a'];
for(int t=now; t&&~val[t]; t=fail[t])
ans+=val[t],val[t]=-1;
}
return ans;
}
} AC;