【题目链接】
http://www.lydsy.com/JudgeOnline/problem.php?id=3172
【题意】
题目的意思是这样的,给若干个单词,求每个单词在这一堆单词中的出现次数。 出题人语文水平高
【思路】
AC自动机. fail树
AC自动机中的fail指针指向该串的一个后缀,将fail指针反向后得到一棵fail树,利用getFail后的bfs序在树上进行DP统计出现次数。
在fail树上,父节点对应字符串是其子结点对应字符串的极大后缀。我们用sum[u]记录一个结点u被几个单词结点所经过,插入时顺便统计一下即可。设pos[i]为单词i在自动机上所对应的尾节点,那么这时候sum[pos[i]]是否为i的答案呢?不是。因为可能出现有一个字符串为abbabc,而i是abc的情况,这时候abc作为后缀出现但是并没有计数,对于结点u,我们应该将fail树上u->root路径上的所有节点的sum+=sum[u],这步操作只需要递推一下,这时候的sum[pos[i]]才是i的答案。
感觉与SAM中的p=>p->fa的思路挺像的。
【代码】
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 5 const int N = 1e6+10; 6 7 struct ACauto { 8 int sz,ch[N][26],sum[N],q[N],pos[N],f[N]; 9 void init() { 10 sz=1; 11 memset(ch[0],0,sizeof(ch[0])); 12 } 13 void insert(char* s,int rank) { 14 int u=0; 15 for(int i=0;s[i];i++) { 16 int c=s[i]-'a'; 17 if(!ch[u][c]) { 18 memset(ch[sz],0,sizeof(ch[sz])); 19 ch[u][c]=sz++; 20 } 21 u=ch[u][c]; 22 sum[u]++; 23 } 24 pos[rank]=u; 25 } 26 void get_Fail() { 27 int front=1,rear=1; //a pos for 0 28 f[0]=1; q[0]=1; 29 for(int i=0,p;i<26;i++) 30 if(p=ch[0][i]) f[p]=0,q[rear++]=p; 31 while(front!=rear) { 32 int qr=q[front++]; 33 for(int c=0;c<26;c++) { 34 int u=ch[qr][c]; 35 if(!u) continue; 36 q[rear++]=u; int v=f[qr]; 37 while(v&&!ch[v][c]) v=f[v]; 38 f[u]=ch[v][c]; 39 } 40 } 41 for(int i=rear-1;i>=0;i--) 42 sum[f[q[i]]]+=sum[q[i]]; 43 } 44 }ac; 45 46 int n; 47 char s[N]; 48 49 int main() { 50 scanf("%d",&n); 51 ac.init(); 52 for(int i=1;i<=n;i++) { 53 scanf("%s",s); 54 ac.insert(s,i); 55 } 56 ac.get_Fail(); 57 for(int i=1;i<=n;i++) 58 printf("%d ",ac.sum[ac.pos[i]]); 59 return 0; 60 }