AC自动机
Posted genius777
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AC自动机相关的知识,希望对你有一定的参考价值。
本来是早就该学的知识点了,但是拖了好长时间最近在重新捡起来。。。
【AC自动机???自动AC机???】
刚学OI的时候,就听到学长说AC自动机,第一次听到这个名字还以为只是开玩笑说用来自动A题的BUG。。。(相信总会有人和我想法是一样的)
AC自动机就是字典树和KMP算法的结合,KMP实现的是单模匹配,也就是一个模式串与文本串进行匹配,但是如果我们有很多模式串怎么办呢?
这时我们就要引入字典树(发现树结构在OI学习中贼优越的样子)
字典树就是一棵可以表示所有出现单词的树,就像字典一样可以进行单词的查询!!!这样就可以实现多模匹配。
但是要是我们一个个跳着来匹配字符那就GG了,和单模匹配一样的暴力,所以我们就要用KMP的失配指针来优化,定义失配指针为fail
首先我们来看看字典树是怎么建立的(如:AAC,CAA,CAC,BA)
那么加上其失配指针后:
这就是将序列上的KMP变成了树上的AC自动机了 !!!
接下来我们来看看AC自动机怎么实现的。。。
首先我们要建立一棵字典树,Trie树:(这应该是基本操作,不会的就要看看字典树了)
1.存储结构
1 struct sd{ 2 int son[30],end,fail;// 每个节点的下一个字符对应的节点,如果当前节点是一个单词的结尾那就记下来,失配指针 3 }trie[20005]; 4 char txt[1000005],ch[155][75];//模式串,文本串 5 int vis[155],maxx,cnt,n;//每个单词的出现次数
2.建立字典树
1 void insert(int v) 2 { 3 int len=strlen(ch[v]),node=0;//用来跳的指针 4 for(int i=0;i<len;i++) 5 { 6 int b=ch[v][i]-\'a\'; 7 if(!trie[node].son[b])//如果没出现过那就新建一个节点 8 trie[node].son[b]=++cnt; 9 node=trie[node].son[b]; 10 } 11 trie[node].end=v; 12 }
3.预处理fail指针
1 void getfail() 2 { 3 queue<int> que;//队列来BFS 4 que.push(0); 5 while(!que.empty()) 6 { 7 int v=que.front();que.pop(); 8 for(int i=0;i<26;i++) 9 { 10 if(trie[v].son[i]) 11 { 12 int p=trie[v].fail;//如果有失配指针那么从失配指针开始配 13 while(p!=-1) 14 { 15 if(trie[p].son[i]) 16 {trie[trie[v].son[i]].fail=trie[p].son[i];break;}//显然一个节点的失配指针的子节点也有这个节点的子节点,那就连上失配指针 17 p=trie[p].fail; 18 } 19 if(p==-1) trie[trie[v].son[i]].fail=0; 20 que.push(trie[v].son[i]);//压入队列 21 } 22 } 23 } 24 }
4.开始匹配
1 void cmp() 2 { 3 int len=strlen(txt); 4 int a=0;//匹配指针 5 for(int i=0;i<len;i++) 6 { 7 int b=txt[i]-\'a\'; 8 while(a&&!trie[a].son[b]) a=trie[a].fail;//如果失配了,那就跳到失配的地方去 9 if(trie[a].son[b]) a=trie[a].son[b]; 10 int p=a; 11 while(p) //在匹配指针的地方匹配一下 12 { 13 if(trie[p].end) //如果有单词的结尾说明已经在字典树上匹配到了一个单词 14 { 15 vis[trie[p].end]++;//这个单词就多出现了一次 16 maxx=max(maxx,vis[trie[p].end]);//取个极值 17 } 18 p=trie[p].fail; 19 } 20 } 21 }
可以看看一道模板题
【代码实现】
1 #include<cstdio> 2 #include<cstring> 3 #include<queue> 4 using namespace std; 5 struct sd{ 6 int son[30],end,fail,cot; 7 }trie[20005]; 8 char txt[1000005],ch[155][75]; 9 int vis[155],maxx,cnt,n; 10 void clear() 11 { 12 memset(trie,0,sizeof(trie)); 13 memset(vis,0,sizeof(vis)); 14 cnt=0,maxx=0,trie[0].fail=-1; 15 } 16 void insert(int v) 17 { 18 int len=strlen(ch[v]),node=0; 19 for(int i=0;i<len;i++) 20 { 21 int b=ch[v][i]-\'a\'; 22 if(!trie[node].son[b]) 23 trie[node].son[b]=++cnt; 24 node=trie[node].son[b]; 25 } 26 trie[node].end=v; 27 } 28 void getfail() 29 { 30 queue<int> que; 31 que.push(0); 32 while(!que.empty()) 33 { 34 int v=que.front();que.pop(); 35 for(int i=0;i<26;i++) 36 { 37 if(trie[v].son[i]) 38 { 39 int p=trie[v].fail; 40 while(p!=-1) 41 { 42 if(trie[p].son[i]) 43 {trie[trie[v].son[i]].fail=trie[p].son[i];break;} 44 p=trie[p].fail; 45 } 46 if(p==-1) trie[trie[v].son[i]].fail=0; 47 que.push(trie[v].son[i]); 48 } 49 } 50 } 51 } 52 void cmp() 53 { 54 int len=strlen(txt); 55 int a=0; 56 for(int i=0;i<len;i++) 57 { 58 int b=txt[i]-\'a\'; 59 while(a&&!trie[a].son[b]) a=trie[a].fail; 60 if(trie[a].son[b]) a=trie[a].son[b]; 61 int p=a; 62 while(p) 63 { 64 if(trie[p].end) 65 { 66 vis[trie[p].end]++; 67 maxx=max(maxx,vis[trie[p].end]); 68 } 69 p=trie[p].fail; 70 } 71 } 72 } 73 int main() 74 { 75 while(scanf("%d",&n)) 76 { 77 if(!n) break; 78 clear(); 79 for(int i=1;i<=n;i++) 80 scanf("%s",ch[i]),insert(i); 81 getfail(); 82 scanf("%s",txt); 83 cmp(); 84 printf("%d\\n",maxx); 85 for(int i=1;i<=n;i++) 86 if(vis[i]==maxx) printf("%s\\n",ch[i]); 87 } 88 return 0; 89 }
以上是关于AC自动机的主要内容,如果未能解决你的问题,请参考以下文章
HDU4057 Rescue the Rabbit(AC自动机+状压DP)
Codeforces 86C Genetic engineering(AC自动机+DP)
POJ1699 Best Sequence(AC自动机+状压DP)