山东济南彤昌机械科技有限公司 山东济南江鹏工贸游有限公司

bzoj 3172 [Tjoi2013]单词(fail树,DP)

 

【题目链接】

 

  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\n",ac.sum[ac.pos[i]]);
59     return 0;
60 }

 

posted on 2016-02-20 08:56  hahalidaxin  阅读(249)  评论(0编辑  收藏  举报