bzoj 3172 AC自动机
初学AC自动机,要先对于每一个模式串求出来trie树,在此基础上构建fail指针,然后在trie树加上失配边构建出整张trie图。
AC自动机的原理和KMP差不多,一个节点的fail指针就是指向trie树上一个最长前缀等于这个单词的后缀。
首先fail[0]=0,然后把0有的每个孩子push进一个队列里(如果直接push(0)的话会出现f[x]=x),然后bfs。
设一个节点为u,父亲为v,那么如果ch[fail[v]][u是v的哪个孩子]!=0,那么fail[u]就等于它,否则就不停的跳fail直到0或存在这个孩子。
但我们可以用trie图来优化这个过程,每个节点的每个孩子都不为空,如果原本为空就直接指向上一步用while循环求的那个孩子(具体看代码),于是跳的时候无脑跳一步就行了。
考虑这道题,一个单词一定为trie上的一个节点,而且它出现在文章中一定是一个前缀的后缀,所以那些fail指针直接或间接指向它的节点一定包含这个前缀,所以用bfs序倒着dp一遍就行了。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<queue> 6 #define N 1000005 7 using namespace std; 8 int n; 9 char s[N]; 10 int cnt; 11 int ch[N][26],f[N],ans[N],sum[N]; 12 int b[N]; 13 void in(int xx) 14 { 15 int now=0,l=strlen(s); 16 for(int i=0;i<l;i++) 17 { 18 int x=s[i]-'a'; 19 if(!ch[now][x])ch[now][x]=++cnt; 20 now=ch[now][x];sum[now]++; 21 } 22 b[xx]=now; 23 } 24 queue<int>q;int a[N];int tot; 25 void fail() 26 { 27 f[0]=0; 28 for(int i=0;i<26;i++) 29 { 30 int u=ch[0][i]; 31 if(u)q.push(u),a[++tot]=u; 32 } 33 while(!q.empty()) 34 { 35 int tmp=q.front();q.pop();a[++tot]=tmp; 36 for(int i=0;i<26;i++) 37 { 38 int u=ch[tmp][i]; 39 if(!u) 40 { 41 ch[tmp][i]=ch[f[tmp]][i];continue; 42 } 43 q.push(u);f[u]=ch[f[tmp]][i]; 44 } 45 } 46 } 47 int main() 48 { 49 scanf("%d",&n); 50 for(int i=1;i<=n;i++) 51 { 52 scanf("%s",s); 53 in(i); 54 } 55 fail(); 56 for(int i=tot;i>=1;i--)sum[f[a[i]]]+=sum[a[i]]; 57 for(int i=1;i<=n;i++) 58 { 59 printf("%d\n",sum[b[i]]); 60 } 61 return 0; 62 }