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;
}

浙公网安备 33010602011771号