BZOJ3172: [Tjoi2013]单词
Description
某人读论文,一篇论文是由许多单词组成。但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次。
Input
第一个一个整数N,表示有多少个单词,接下来N行每行一个单词。每个单词由小写字母组成,N<=200,单词长度不超过10^6
Output
输出N个整数,第i行的数字表示第i个单词在文章中出现了多少次。
Sample Input
3
a
aa
aaa
a
aa
aaa
Sample Output
6
3
1
3
1
题解Here!
本来是想找后缀数组的,然后找到了一道$AC$自动机的裸题。。。
把所有单词扔到$AC$自动机里。 当$insert$的时候记录这条路径上的所有点$val++$。
设$p$的$fail$指针指向$q$。
因为根到$p$的路径的某一后缀等同于根到$q$,所以$p$节点为$q$节点贡献的答案是$val[p]$。
具体操作就是记录$buildtree$时的队列,逆序处理为其的$fail$指针贡献答案。
有点像拓扑排序。。。
因为在队列里的节点的深度是单调不下降的,所以这么做是对的。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<queue> #define MAXN 1000010 #define MAXM 210 using namespace std; int n,m=0,size=0; int id[MAXM],num[MAXN]; char ch[MAXN]; struct AC{ int fail,val,son[26]; AC(){ fail=val=0; memset(son,0,sizeof(son)); } }a[MAXN]; inline int read(){ int date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } inline int idx(char x){return x-'a';} int insert(char *s){ int u=0,l=strlen(s); for(int i=0;i<l;i++){ int c=idx(s[i]); if(!a[u].son[c])a[u].son[c]=++size; u=a[u].son[c]; a[u].val++; } return u; } void buildtree(){ int u,v; queue<int> q; num[++m]=0; for(int i=0;i<26;i++) if(a[0].son[i]){ a[a[0].son[i]].fail=0; q.push(a[0].son[i]); } while(!q.empty()){ u=q.front(); q.pop(); num[++m]=u; for(int i=0;i<26;i++){ if(a[u].son[i]){ a[a[u].son[i]].fail=a[a[u].fail].son[i]; q.push(a[u].son[i]); } else a[u].son[i]=a[a[u].fail].son[i]; } } } void query(){ for(int i=m;i>=1;i--)a[a[num[i]].fail].val+=a[num[i]].val; } void work(){ query(); for(int i=1;i<=n;i++)printf("%d\n",a[id[i]].val); } void init(){ n=read(); for(int i=1;i<=n;i++){ scanf("%s",ch); id[i]=insert(ch); } buildtree(); } int main(){ init(); work(); return 0; }