ac自动机通常用来解决多字符串匹配问题,如从字符串s找字符串t[i](i<=n),如果直接用KMP那么时间复杂度为,而用ac自动机时间复杂度为。
ac自动机可以认为是kmp和trie的结合,因为ac自动机就是在trie的基础上怎加了fail变量,fail指向的是当前字符串的最长后缀的尾节点,
作用就是在当前匹配失败时,将当前指针转向fail指向的位置,继续匹配,这样就避免了重复匹配,类似于kmp的next数组的作用。
下面是HDU 2222 Keywords Search模板代码,求得是出现在主串中的子串个数(重复出现算一次)。
代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 #include<algorithm> 6 using namespace std; 7 const int N=1e6+5; 8 const int M=5e5+5; 9 10 int idx; 11 int trie[M][26],fail[M],cnt[M]; 12 char s[N]; 13 14 void init(){ 15 idx=0; 16 memset(trie,-1,sizeof(trie)); 17 memset(fail,0,sizeof(fail)); 18 memset(cnt,0,sizeof(cnt)); 19 } 20 21 //插入模式串 22 void Insert(char *s){ 23 int len=strlen(s); 24 int now=0; 25 for(int i=0;i<len;i++){ 26 int ch=s[i]-'a'; 27 if(trie[now][ch]==-1) 28 trie[now][ch]=++idx; 29 now=trie[now][ch]; 30 } 31 cnt[now]++; 32 } 33 34 //获得fail指针 35 void getfail(){ 36 fail[0]=0; 37 queue<int>q; 38 for(int i=0;i<26;i++){ 39 if(trie[0][i]==-1) 40 trie[0][i]=0; 41 else{ 42 fail[trie[0][i]]=0; 43 q.push(trie[0][i]); 44 } 45 } 46 while(!q.empty()){ 47 int u=q.front(); 48 q.pop(); 49 for(int i=0;i<26;i++){ 50 if(trie[u][i]!=-1){ 51 fail[trie[u][i]]=trie[fail[u]][i]; 52 q.push(trie[u][i]); 53 } 54 else trie[u][i]=trie[fail[u]][i]; 55 } 56 } 57 } 58 59 //这里的意思是如果a出现在s中,那么作为a后缀的b肯定也在s中,所以不断找后缀 60 int get(int u){ 61 int res=0; 62 while(u){ 63 res+=cnt[u]; 64 cnt[u]=0; 65 u=fail[u]; 66 } 67 return res; 68 } 69 70 //字符串匹配 71 int match(char *s){ 72 int len=strlen(s),now=0,ans=0; 73 for(int i=0;i<len;i++){ 74 int ch=s[i]-'a'; 75 now=trie[now][ch]; 76 //if(cnt[now]) 注意这里不能判断cnt[now]>0,比如在she中找模式串she、h其中h是sh的的后缀而cnt[节点h]=0。 77 ans+=get(now); 78 } 79 return ans; 80 } 81 82 int main(){ 83 int t; 84 scanf("%d",&t); 85 while(t--){ 86 init(); 87 int n; 88 scanf("%d",&n); 89 for(int i=0;i<n;i++){ 90 scanf("%s",s); 91 Insert(s); 92 } 93 scanf("%s",s); 94 getfail(); 95 printf("%d ",match(s)); 96 } 97 return 0; 98 }