AC自动机就是在trie树上的每一个节点上加了一个fail指针,用来多串匹配,类似fail数组。
模板题:hdu2222 给出n个串,然后给一篇文章,问这n个串有多少个在文章里面出现过。
首先我们把这n个串搞成一棵AC自动机,然后在这上面匹配这一篇文章。
(盗图一张)
首先我们先建成一棵单纯的trie,然后每个结束节点做点标记啥的。
然后我们来构造失败指针。我们在这颗trie上面bfs。比如我们现在要为一个新节点决定fail指针,如果这个节点上的字母为c,我们就沿着他父亲的失败指针走,直到走到一个节点,他的儿子中也有字母为c的节点。然后把当前节点的失败指针指向那个字母也为c的儿子。如果一直走到了root都没找到,那就把失败指针指向root。然后把这个节点加入队列,然后继续bfs它的孩子。然后如何把一段文字拿去匹配呢?我们开始在root,然后每次往下面匹配一个字符,匹配不到就往fail走。一直到走到某一个节点下面有这个字符了就往下面走,否则就走到root。然后我们把这个节点按照fail遍历一圈,所有这些子串都可以被匹配到。(注意此时不要改变当前节点)
(如果窝讲不清楚这里似乎讲得比较清楚
下面这份代码有一点实现上的技巧,如果一个点的next为空,它就会指向跳若干次fail之后的那个点…具体看代码
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <algorithm> #include <string.h> #include <vector> #include <limits> #include <set> #include <map> using namespace std; #define SZ 1000099 int rot=1,ch[SZ][29],fail[SZ],cnt[SZ],e=1; void insert(char* s) { int cur=rot; for(int i=0;s[i];i++) { int c=s[i]-'a'; if(!ch[cur][c]) ch[cur][c]=++e; cur=ch[cur][c]; } cnt[cur]++; } int qs[SZ],h=0,t=0; void bfail() { h=t=0; fail[rot]=rot; for(int i=0;i<26;i++) { if(!ch[rot][i]) { ch[rot][i]=rot; continue; } fail[ch[rot][i]]=rot; qs[t++]=ch[rot][i]; } while(h!=t) { int cur=qs[h++]; for(int c=0;c<26;c++) { if(!ch[cur][c]) ch[cur][c]=ch[fail[cur]][c]; else { fail[ch[cur][c]]=ch[fail[cur]][c]; qs[t++]=ch[cur][c]; } } } } int match(char* s) { int cur=rot,ans=0; for(int i=0;s[i];i++) { int c=s[i]-'a'; cur=ch[cur][c]; for(int f=cur;f!=rot;f=fail[f]) ans+=cnt[f], cnt[f]=0; } return ans; } int n,T; char str[SZ]; int main() { scanf("%d",&T); while(T--) { rot=e=1; memset(ch,0,sizeof(ch)); memset(fail,0,sizeof(fail)); memset(cnt,0,sizeof(cnt)); scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%s",str); insert(str); } bfail(); scanf("%s",str); printf("%d ",match(str)); } }
然后有一个AC自动机的专题里面都是AC自动机有关的题目大家可以做一下:
http://acm.hust.edu.cn/vjudge/contest/view.action?cid=25605#overview
等窝做得差不多了再来贴代码。
UPD:稍微做了几题,弃坑啦。
代码可以戳这里看到。(懒得拷上来了
题解可以戳出这套题的神犇博客。