hdu2222 Keywords Search(AC自动机初步)
题目大意:
给出多个模式串和一个主串,求多少个模式串在主串中出现过。
这是一道AC自动机的模板题。
在学习AC自动机之前,首先要学习WA自动机、TLE自动机和MLE自动机(雾
AC自动机是一种多模式串匹配算法。
AC自动机概述:
*fail指针:指向失配时的匹配节点;
1)构建字典树
2)初始化fail指针:
一条$fail$指针链可以理解为一个串连所有后缀相同的字符串的链表,并且所有链表的末端都指向trie的根。我们定义沿着节点$v$的$fail$指针走到根部的路径为v的trie链表。
虚线为$fail$指针,红色节点为AC-DFA的接收态。
使用BFS构造fail指针:初始化队列为树根。对于trie上的每个非叶子节点$u$的孩子$v$,我们沿着其fail链表向上走,直到某个节点的孩子$x=v$,则$v->fail=x$。将$v$加入队列。
特别的,对于$p->nxt[i]==NULL$的节点,令$p->nxt[i]=p->fail->nxt[i]$
3)由于初始化$fail$指针的时候,令$p->nxt[i]=p->fail->nxt[i]$,所以现在的trie树变成了一个trie图,所有节点的$nxt$指针都不为空。我们按照主串,沿着相应的路径向走trie图,每访问一个节点就遍历该节点的$fail$链表并计数。
代码:
1 #include<cstring> 2 #include<cstdio> 3 #include<cmath> 4 #include<algorithm> 5 #include<cctype> 6 #define foru(i,x,y) for(int i=x;i<=y;i++) 7 const int N=1e6+10; 8 9 struct trie{ 10 trie *nxt[26]; 11 trie *fail; 12 int v; 13 void init(){v=0;foru(i,0,25)nxt[i]=NULL;fail=NULL;} 14 }; 15 trie *r,*q[600000]; 16 int T,n; 17 char ch[N]; 18 void add(char *s){//建trie 19 trie *k=r,*p; 20 int l=strlen(s); 21 foru(i,0,l-1){ 22 int id=s[i]-'a'; 23 if(!k->nxt[id]){ 24 p=(trie*)malloc(sizeof(trie)); 25 p->init(); 26 k->nxt[id]=p; 27 k=p; 28 }else{ 29 k=k->nxt[id]; 30 } 31 } 32 k->v++;//标记为接收态 33 } 34 35 void setfail(){ 36 trie *k; 37 int s=1,t=0; 38 q[++t]=r; 39 while(s<=t){ 40 k=q[s++]; 41 foru(i,0,25){ 42 if(k->nxt[i]){ 43 trie *p=k->fail; 44 while(p&&!p->nxt[i])p=p->fail;//寻找上一个相同后缀的字符串 45 k->nxt[i]->fail=(!p?r:p->nxt[i]);//不存在则指向trie根 46 q[++t]=k->nxt[i]; 47 }else 48 k->nxt[i]=(k==r?r:k->fail->nxt[i]); 49 } 50 } 51 } 52 53 int find(char *s){ 54 trie *k=r,*p;int ret=0; 55 int l=strlen(s); 56 foru(i,0,l-1){ 57 int id=s[i]-'a'; 58 k=k->nxt[id]; 59 p=k; 60 while(p){//遍历fail链 61 ret+=p->v; 62 p->v=0;//避免重复计数 63 p=p->fail; 64 } 65 } 66 return ret; 67 } 68 69 int main(){ 70 scanf("%d",&T); 71 while(T--){ 72 r=(trie*)malloc(sizeof(trie)); 73 r->init(); 74 scanf("%d",&n); 75 foru(i,1,n){ 76 scanf("%s",ch); 77 add(ch); 78 } 79 setfail(); 80 scanf("%s",ch); 81 printf("%d\n",find(ch)); 82 } 83 }