emm~ AC自动机好像看起来好高级好难得样子QAQ
其实,只要你能够想明白,这个还是挺简单的
知识储备
KMP算法
Trie树
其实都挺简单的(逃~
进入正题
AC自动机吧,主要就是在一个串中匹配多个字符串的算法
KMP则只能匹配一个字符串
Trie能帮你把多个字串组合起来
下文中的数组先在这里解释一下
now->当前节点
n->字符串长度
ch[now][c]->now节点的c儿子(对应的字符是c+’a’)
val[now]->有多少个字符串在当前位置结束
fail[now]当前节点的失配位置
对于要匹配的字符串,我们用Trie先把它组合起来
void insert(char *s) {
int now=0,n=strlen(s);
for(int i=0;i<n;i++) {
int c=s[i]-'a';
if(!ch[now][c]) ch[now][c]=++nodecnt;
now=ch[now][c];
}
val[now]++;
return ;
}
很简单吧
想想KMP,就是对于每个点求出next数组然后快速匹配
那么AC自动机呢?
不就是对Trie上每个点求出next么?
然后最重要的来了->求失配函数fail(其实就差不多是KMP中的next)
我们利用BFS的方法来遍历整棵Trie,为什么呢?KMP中的转移会使得转以后序列比转移前长么?肯定是向前转移啊,而BFS保证当前所有比当前节点短的节点都已经处理完毕了
对于根节点的儿子们来说,所有的fail都为0指向根节点(你想转移到哪去?)
如果该节点有这个儿子,它儿子的fail就可以设为从该节点fail的儿子
如果该节点没有这个儿子,那么我们就帮它补上这个儿子!,这个儿子从哪来呢?就是该节点fail的儿子
ch数组可以这样理解:ch[x][i]是指从根节点遍历到x这个节点所得到的串和一个是它的最长后缀的串的i儿子,其中这个最长后缀串可以是他本身(但是也许它自己没有这个儿子就不行)
P.S:好像有点难懂,具体看代码
void build() {
for(int i=0;i<26;i++) if(ch[0][i]) fail[ch[0][i]]=0,q.push(ch[0][i]);
while(!q.empty()) {
int u=q.front();q.pop();
for(int i=0;i<26;i++) {
if(ch[u][i]) fail[ch[u][i]]=ch[fail[u]][i],q.push(ch[u][i]);
else ch[u][i]=ch[fail[u]][i];
}
}
}
最后一步是匹配
每次匹配不仅仅是当前节点与其匹配,我们还用通过fail指针看看是不是还有已经能够匹配的节点
剩下的就和KMP的匹配差不多了
void query(char *str) {
int u=0,n=strlen(str),res=0;
for(int i=0;i<n;i++) {
int c=str[i]-'a';
u=ch[u][c];
if(val[u]) cnt[val[u]]++;
int v=fail[u];
while(v) {
if(val[v]) cnt[val[v]]++;
v=fail[v];
}
}
}
神奇的AC自动机,神奇的题目
稍后我会添加一些AC自动机的题目上来,敬请期待
本文深入浅出地介绍了AC自动机的原理与实现过程,包括如何使用Trie树和KMP算法构建AC自动机,以及如何进行高效匹配。此外,还详细讲解了失配函数fail的求解方法。
530

被折叠的 条评论
为什么被折叠?



