AC自动机讲解
三步走:
①将所有的模式串构成一颗Trie树。
②对Trie上所有的节点构造前缀指针。
③利用前缀指针Fail对主串进行匹配。
实际上这个前缀指针Fail与KMP算法中的nxt数组非常相似,因此AC自动机可以看作是Trie与KMP算法的结合(Trie上的KMP算法)。
第一步:字典树的构建.
第二步:
找Fail指针:
运用广度优先搜索 (BFS)来求得。
对于与根节点直接相连的点来说,如果这些节点失配,他们的Fail指针直接指向root即可。
其他节点其Fail指针求法如下:
设当前节点为father,其子节点为child。
求child的Fail指针时,首先我们要找到其father的Fail指针所指向的节点设为F,看F的孩子中有没有和child节点所表示的字母相同的节点,如果有的话,这个节
点就是child的Fail指针,如果没有,则需要再找到F的Fail指针所指向的节点,如果一直找都找不到,则child的Fail指针就要指向root。
第三步:
文本串的匹配:
匹配过程分两种情况:
(1)当前字符匹配,从当前节点沿着树边有一条路径可以到达目标字符,如果当前匹配的字符是一个单词的结尾,就沿着当前字符的Fail指针,一直遍历到根,如果这些节点末尾有标记(当前节点单词末尾的标记),这些节点全都是可以匹配上的节点。统计完毕后,并将那些节点标记。此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;
(2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,当指针指向root时结束。
重复这2个过程中的任意一个,直到模式串走到结尾为止。
eg:
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n; 4 char s[2000001]; 5 int trie[1000001][30]; 6 int que[1000001],end[1000001],nxt[1000001]; 7 int ans,cnt; 8 void insert(char *str)//Trie树构建过程 9 { 10 int p=1; 11 int len=strlen(str); 12 for(int i=0;i<len;i++) 13 { 14 int ch=str[i]-'a'; 15 if(!trie[p][ch]) 16 { 17 trie[p][ch]=++cnt; 18 memset(trie[cnt],0,sizeof(trie[cnt]));//每次只需要清空我们会用得到的行 19 } 20 p=trie[p][ch]; 21 } 22 end[p]++;//因为有可能会有重复的单词,故在此end统计在此有多少个单词结束,而不是有没有单词结束 23 } 24 void build()//BFS构建Fail指针 25 { 26 for(int i=0;i<26;i++)//为了方便将0的所有转一遍都设为根节点1 27 trie[0][i]=1; 28 nxt[1]=0;//若在根节点失配, 则无法匹配字符 29 que[1]=1; 30 int head=1,tail=1; 31 while(head<=tail) 32 { 33 for(int i=0;i<26;i++) 34 if(!trie[que[head]][i])trie[que[head]][i]=trie[nxt[que[head]]][i];//注意这里,下面会有详细解释 35 else 36 { 37 que[++tail]=trie[que[head]][i]; 38 int flag=nxt[que[head]]; 39 while(flag&&!trie[flag][i])flag=nxt[flag];//循环往前找 40 nxt[trie[que[head]][i]]=trie[nxt[que[head]]][i]; 41 } 42 head++;//注意队头++ 43 } 44 } 45 void find(char *str)//匹配 46 { 47 int p=1; 48 int len=strlen(str); 49 for(int i=0;i<len;i++) 50 { 51 int flag=p=trie[p][str[i]-'a']; 52 while(end[flag]!=-1&&flag) 53 { 54 ans+=end[flag]; 55 end[flag]=-1;//标记这个点已经访问过,以后不再访问 56 flag=nxt[flag]; 57 } 58 } 59 } 60 int main() 61 { 62 int T; 63 scanf("%d",&T); 64 while(T--) 65 { 66 memset(end,0,sizeof(end));//多测不清空,爆零两行泪(宝宝别哭) 67 cnt=1; 68 ans=0; 69 for(int i=0;i<26;i++) 70 trie[0][i]=1,trie[1][i]=0;//亦是清空 71 scanf("%d",&n); 72 for(int i=1;i<=n;i++) 73 { 74 scanf("%s",s); 75 insert(s);//读入子串并插入Trie树 76 } 77 build(); 78 scanf("%s",s); 79 find(s);//匹配 80 printf("%d\n",ans); 81 } 82 return 0; 83 }
浙公网安备 33010602011771号