bzoj

链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3172
思路:学到一种新思路,以前都是用父亲节点信息更新子节点,这样可以维护出trie树上某点包括那些单词节点。这次的是先拿询问串来建AC自动机,注意插入trie树时经过的路径都计数++,然后我们按照fail树的拓扑序从下到上累加,因为一个点指向的点一定是它的某个前缀,一个串有几个能与该前缀匹配的点就会产生多少个链接过去。这样累加后查询下在该点的累加值就是出现次数的答案。
所以套路就是:对于给出一些串和询问串,把询问串拿来建trie,然后串拿到trie上去跑,并将跑过的点计数++,然后拓扑序在fail累加,最后看询问串的单词节点处的计数值就是出现的次数。

代码:

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

using namespace std;

const int maxnode = 1e6+100;
const int sigma_size = 26;
int n;
char s[maxnode];

int ch[maxnode][sigma_size];
int val[maxnode];
int f[maxnode];
int last[maxnode];
int sz;
int cnt[maxnode];
int id[maxnode];
int que[maxnode], qt;

void () {
sz = 1;
memset(ch[0], 0, sizeof(ch[0]));
}

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

void insert(char *s, int x, int v = 1) {
int n = strlen(s);
int u = 0;
for (int i = 0; i < n; i++) {
int c = idx(s[i]);
if (!ch[u][c]) {
ch[u][c] = sz;
memset(ch[sz], 0, sizeof(ch[sz]));
val[sz++] = 0;
}
u = ch[u][c];
cnt[u]++;
}
val[u] = v;
id[x] = u;
}

void getfail() {
queue<int> q;
f[0] = 0;
for (int i = 0; i < sigma_size; i++) {
int u = ch[0][i];
if (u) {
f[u] = last[u] = 0;
q.push(u);
que[++qt] = u;
}
}
while (!q.empty()) {
int r = q.front();
q.pop();

for (int c = 0; c < sigma_size; c++) {
int u = ch[r][c];
if (!u) {
ch[r][c] = ch[f[r]][c];
continue;
}
q.push(u);
que[++qt] = u;
int v = f[r];
while (v && !ch[v][c])v = f[v];
f[u] = ch[v][c];
last[u] = val[f[u]] ? f[u] : last[f[u]];
}
}
for(int i = qt; i; i--) cnt[f[que[i]]] += cnt[que[i]];
}

int main(){
init();
scanf("%d", &n);
for(int i = 1; i <= n; i++){
scanf("%s", s);
insert(s, i);
}
getfail();
for(int i = 1; i <= n; i++) printf("%dn", cnt[id[i]]);
return 0;
}