[TJOI2013]单词(AC自动机+前缀和维护)
链接:https://ac.nowcoder.com/acm/problem/20443
来源:牛客网
题目描述
某人读论文,一篇论文是由许多单词组成。但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次。
输入描述:
第一个一个整数N,表示有多少个单词,接下来N行每行一个单词。每个单词由小写字母组成,N ≤ 200,单词长度不超过10^6
输出描述:
输出N个整数,第i行的数字表示第i个单词在文章中出现了多少次。
具体思路:首先在trie树上每次添加一个节点,这个节点的权值就+1,保存每个字符串最终节点在trie树上的编号。然后再去建立fail指针。当建立好fail指针的时候,跑一遍前缀和。
具体跑前缀和的时候,每一个节点更新到这个节点的fail指针上就好了。
AC代码:
1 #include<bits/stdc++.h>
2 using namespace std;
3 # define ll long long
4 const int mod = 1e9+7;
5 const int maxn = 1e6+100;
6 int ch[maxn][30],tot=0;
7 char str[200+10][maxn];
8 int val[maxn];
9 int ans[maxn];
10 int add_trie(char st[])
11 {
12 int len=strlen(st);
13 int p=0;
14 for(int i=0; i<len; i++)
15 {
16 int to=st[i]-'a';
17 if(!ch[p][to])
18 ch[p][to]=++tot;
19 p=ch[p][to];
20 val[p]++;
21 }
22 return p;
23 }
24 int fail[maxn],last[maxn];
25 int sto[maxn];
26 int num=0;
27 void get_fail()
28 {
29 queue<int>q;
30 for(int i=0; i<=tot; i++)
31 fail[i]=-1;
32 q.push(0);
33 while(!q.empty())
34 {
35 int top=q.front();
36 q.pop();
37 for(int i=0; i<26; i++)
38 {
39 int u=ch[top][i];
40 if(u==0)
41 continue;
42 sto[++num]=u;
43 q.push(u);
44 int v=fail[top];
45 while(v!=-1&&!ch[v][i])
46 v=fail[v];
47 fail[u]=(v==-1?0:ch[v][i]);
48 last[u]=val[fail[u]]?fail[u]:last[fail[u]];
49 }
50 }
51 }
52 int n;
53 void init()
54 {
55 for(int i=num; i>=1; i--)
56 {
57 val[fail[sto[i]]]+=val[sto[i]];
58 }
59 }
60 void print()
61 {
62 for(int i=1; i<=n; i++)
63 {
64 printf("%d\n",val[ans[i]]);
65 }
66 }
67 int main()
68 {
69 scanf("%d",&n);
70 for(int i=1; i<=n; i++)
71 {
72 scanf("%s",str[i]);
73 ans[i] = add_trie(str[i]);
74 }
75 get_fail();
76 init();
77 print();
78 return 0;
79 }