AK-dream

【题目描述】
某人读论文,一篇论文是由许多单词组成。但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次。

【输入格式】
第一个一个整数 $N$,表示有多少个单词,接下来 $N$ 行每行一个单词。

【输出格式】
输出 $N$ 个整数,第 $i$ 行的数字表示第 $i$ 个单词在文章中出现了多少次。

对于全部数据,$1\le N\le 200$,所有单词长度的和不超过 $10^6$,保证每个单词由小写字母组成。

题解

这道题题目意思让我看半天

总之就是每个输入的单词同样也作为一篇文章 然后最后要你统计 每个词 在 所有词 中共出现了多少次

比如样例

1
2
3
4
3
a
aa
aaa

串$aa$在$a$中出现$0$次 在$aa$中出现$1$次 在$aaa$中出现$2$次 所以是$0+1+2=3$

作为一道AC自动机的模板题,这道题在BZOJ上时限给了10s。。。

实际上不用这么宽的时限 1s就够了

首先先建好AC自动机 查询每个单词在文章中出现次数 一般我们会维护一个指针在AC自动机上跟着文章的字母走 然后每走一个字母都要暴力跳fail统计答案

然而很容易发现这样做的弊端:如果重复经过了AC自动机的同一个节点$k$次 那么就要重复跳$k$次同样的fail链每次加$1$ 不如到最后文章遍历结束后再进行统计

所以就把沿路经过的节点$val+1$,相当于打上一个标记 最后在fail树上累加一下$x$的子树和就是$x$代表的串出现多少次

【代码】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <bits/stdc++.h>
#define re register
using namespace std;

int ans[1000005];

int n, m, len;
string s[1000005];

struct AC_automaton{
int ch[1000005][30], fail[1000005], val[1000005], f[1000005], tot;
vector<int> tag[1000005];

inline int o(char c) { return c - 'a'; }

inline void insert(string str, int id) {
int x = 0;
for (re int i = 0; i < str.size(); i++) {
if (!ch[x][o(str[i])]) {
ch[x][o(str[i])] = ++tot;
}
x = ch[x][o(str[i])];
}
tag[x].push_back(id);
}

queue<int> q;

inline void getfail() {
while (!q.empty()) q.pop();
for (re int i = 0; i < 26; i++) if (ch[0][i]) q.push(ch[0][i]);
while (!q.empty()) {
int x = q.front(); q.pop();
for (re int i = 0; i < 26; i++) {
if (ch[x][i]) fail[ch[x][i]] = ch[fail[x]][i], q.push(ch[x][i]);
else ch[x][i] = ch[fail[x]][i];
}
}
}

inline void solve(string str) {
int now = 0;
for (re int i = 0; i < str.size(); i++) {
now = ch[now][o(str[i])];
val[now]++;
}
}

int in[1000005], b[1000005], cnt;

inline void toposort() {
while (!q.empty()) q.pop();
for (int i = 1; i <= tot; i++) {
in[fail[i]]++;
}
for (int i = 0; i <= tot; i++) {
if (!in[i]) q.push(i);
}
while (!q.empty()) {
int x = q.front(); q.pop();
b[++cnt] = x;
in[fail[x]]--;
if (!in[fail[x]]) q.push(fail[x]);
}
}

inline void getans() {
toposort(); //拓扑排序完后直接累加就可以了
for (int i = 1; i <= cnt; i++) {
int x = b[i];
if (x != 0) val[fail[x]] += val[x];
}
for (int i = 0; i <= tot; i++) {
for (int j = 0; j < tag[i].size(); j++) {
ans[tag[i][j]] += val[i];
}
}
}
} T;

int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n;
for (re int i = 1; i <= n; i++) {
cin >> s[i];
T.insert(s[i], i);
}
T.getfail();
for (re int i = 1; i <= n; i++) {
T.solve(s[i]);
}
T.getans();
for (re int i = 1; i <= n; i++) {
cout << ans[i] << endl;
}
return 0;
}


 评论