【洛谷】3808 【模板】AC自动机(简单版)

来源:互联网 发布:淘宝短网址 编辑:程序博客网 时间:2024/06/09 22:23

题目传送门

终于看懂啦!终于学会了AC自动机啦!(Flag立起)写篇博客梳理一下自己的思路吧。

在文章的开头,必须先放上一些大佬的博客来压压场面:当然是orz ZZK大佬,神犇传送门

然而AC自动机的原理其实是比较好理解的,就是把Trie树和KMP的思想结合起来。这么说起来KMP就是单个字符串的AC自动机啊。(雾)

给出许多模式串,把这些字符串都加入到Trie树中,在每个字符串结尾的叶子节点上计数。

然后在所有模式串都加入Trie树中后开始建立next树,也就是对于所有的模式串做KMP。

首先把和根节点直接相连的节点加入队列中,然后用BFS的思想来确定每个节点的next:取当前的队头,然后枚举这个节点的所有儿子,设当前儿子的字母为ch,当前节点的next为k,取所有存在的儿子,若节点k的ch儿子不为空,则当前节点的儿子的next就等于节点k的ch儿子的编号,否则k=next[k],即保证节点k到根的路径是当前节点到根节点路径的后缀,重复上述操作,直到确定了当前节点的ch儿子。

不过上述求next树的方法过于暴力,可能会TLE,那么我们就会想到上述操作是否有可以优化的地方,答案当然是有的。

依然是去当前的队头,枚举这个节点的所有儿子,若这个儿子存在,那么这个儿子的next就等于节点k的ch儿子,否则这个儿子就等于节点k的ch儿子。

为什么这样的操作可以保证正确性呢?因为我们可以考虑节点k的ch儿子,如果节点k的ch儿子不存在,则把它变成了一个指针,指向它如果存在时匹配的节点。

这样搞了以后就非常方便,不需要每次再向上枚举,浪费时间。

最后就是查询,这题的查询就是求给定的字符串中包含了多少模式串,只要每次向上枚举,答案加上当前节点的计数,然后把当前节点标记为不可取即可。

附上AC代码:

#include <cstdio>#include <cstring>#include <queue>using namespace std;const int N=1000010;struct note{int nt,ed,lk[26];}AC[N];int n,size=0;char s[N];inline void insert(char *s){int len=strlen(s+1),now=0;for (int i=1; i<=len; ++i){if (!AC[now].lk[s[i]-'a']) AC[now].lk[s[i]-'a']=++size;now=AC[now].lk[s[i]-'a'];}++AC[now].ed;return;}inline void build(){queue <int> que;for (int i=0; i<26; ++i)if (AC[0].lk[i]) AC[AC[0].lk[i]].nt=0,que.push(AC[0].lk[i]);while (!que.empty()){int p=que.front();que.pop();for (int i=0; i<26; ++i)if (AC[p].lk[i]) AC[AC[p].lk[i]].nt=AC[AC[p].nt].lk[i],que.push(AC[p].lk[i]);else AC[p].lk[i]=AC[AC[p].nt].lk[i];}return;}inline int query(char *s){int len=strlen(s+1);int now=0,ans=0;for (int i=1; i<=len; ++i){now=AC[now].lk[s[i]-'a'];for (int j=now; j&&AC[j].ed!=-1; j=AC[j].nt)ans+=AC[j].ed,AC[j].ed=-1;}return ans;}int main(void){scanf("%d",&n);for (int i=1; i<=n; ++i) scanf("%s",s+1),insert(s);AC[0].nt=0;build();scanf("%s",s+1),printf("%d",query(s));return 0;}

原创粉丝点击