Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。
解决什么问题呢?
KMP是给你一个模式串和一个文本串,要求求出模式串的匹配位置。
而AC自动机是给你一个文本串和一堆模式串,问你能匹配上多少模式串。
容易想到我们可以在Trie上跑KMP,结合二者的优点就是我们的AC自动机了。
想学AC自动机,先要明白Trie树的实现方法和KMP的思想。
AC自动机大概就长这个样,它是在字典树的基础上多了个失配指针,告诉你失配了该往哪里跑,就相当于KMP中的next数组。
比如我们在K处失配
显然next记录的位置是在k的父亲的next下的s[k]的编号
我们的next是按照从根节点开始的BFS序求解,就是这样。
1 void get_next() 2 { 3 for(int i=0;i<26;i++) 4 { 5 if(trie[0][i]) 6 { 7 next[trie[0][i]]=0; 8 q.push(trie[0][i]); 9 //因为按照BFS序找next嘛,可以理解为什么按BFS序吧 10 } 11 }//先预处理深度为1的点的next,显然他父亲没有与i相同的兄弟,所以直接赋0 12 while(!q.empty()) 13 { 14 int cnt=q.front(); q.pop();//取队首,找他儿子的next 15 for(int i=0;i<26;i++) 16 { 17 if(trie[cnt][i]) 18 { 19 next[trie[cnt][i]]=trie[next[cnt]][i];//显然的转移 20 q.push(trie[cnt][i]); 21 } 22 else 23 trie[cnt][i]=trie[next[cnt]][i];//既然不存在,我们查找它时就应该找到上面那个i的位置 24 //由于是按BFS序找的,所以 trie[next[cnt]][i]一定找过 25 } 26 } 27 }
这就是next的求法。
对了,忘了说了,建AC自动机的方法就是建一颗Trie:
1 void _insert(string s) 2 { 3 int now=0; 4 int size=s.size(); 5 for(int i=0;i) 6 { 7 int x=s[i]-'a'; 8 if(!trie[now][x]) 9 trie[now][x]=++total; 10 now=trie[now][x]; 11 } 12 _word[now]++;//我们令_word记录以now结尾有几个单词,用于解题的。 13 }
如果看官实在看不懂鄙人丑陋的码风,可以转开始的链接-Trie
好,接下来就是查找了:
给我们一个文本串S,问在AC自动机上有多少单词出现在了S上(多模式串匹配)
我们的_word还是很有用的,就是我们每找到一个结尾k,那么就可以保证结尾为k的所有单词都被访问过一遍,所以我们的答案加上_word[k],之后为避免重复,将_word[k]清零。
查找嘛,正常情况就按照查找字典树的办法查找,失配的话就跳到next,同时我们要保证你找到的一定是单词结尾(显然若不存在,程序结束)。
1 int find(string s) 2 { 3 int size=s.size(); 4 int now=0,ans=0; 5 for(int i=0;i) 6 { 7 int x=s[i]-'a'; 8 now=trie[now][x]; 9 for(int k=now;k&&_word[k];k=next[k]) 10 { 11 ans+=_word[k]; 12 _word[k]=0; 13 } 14 } 15 return ans; 16 }
于是就结束了。
//洛谷3808:
#include#include #include #include<string> #include using namespace std; int n,trie[1000010][30],next[1000010],total,_word[10000010]; queue<int>q; void _insert(string s) { int now=0; int size=s.size(); for(int i=0;i ) { int x=s[i]-'a'; if(!trie[now][x]) trie[now][x]=++total; now=trie[now][x]; } _word[now]++; } void get_next() { for(int i=0;i<26;i++) { if(trie[0][i]) { next[trie[0][i]]=0; q.push(trie[0][i]); } } while(!q.empty()) { int cnt=q.front(); q.pop(); for(int i=0;i<26;i++) { if(trie[cnt][i]) { next[trie[cnt][i]]=trie[next[cnt]][i]; q.push(trie[cnt][i]); } else trie[cnt][i]=trie[next[cnt]][i]; } } } int find(string s) { int size=s.size(); int now=0,ans=0; for(int i=0;i ) { int x=s[i]-'a'; now=trie[now][x]; for(int k=now;k&&-(_word[k]+1);k=next[k])//“k&&_word[k]”走到头再找next ,不卡时间过不去 { ans+=_word[k]; _word[k]=-1; } } return ans; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { string s; cin>>s; _insert(s); } get_next(); string s; cin>>s; cout<<find(s); }
进阶版:
不同的是,他让你求哪个串出现的次数最多,而且可能有许多串出现的次数一样多。
因为输入的特殊性,我们在输入时赋予每个串的结尾一个映射belong[i],记录这个串是第几个输入进去的(显然纵然有重复模式串也不影响答案)
然后对于每次查找,我们另数组_time[k]表示k这个字符串出现了几次,在查找时每找到一个点k,我们令_time[belong[k]]++.
最后顺序统计答案。
1 #include2 #include 3 #include 4 #include
AC自动机是一种优秀的多模式串匹配算法,应用广泛,希望大家能深入理解。