题目地址:
https://www.acwing.com/problem/content/1287/
某人读论文,一篇论文是由许多单词组成的。但他发现一个单词会在论文中出现很多次,现在他想知道每个单词分别在论文中出现多少次。这里的论文可以理解为所有单词整体。
输入格式:
第一行一个整数
N
N
N,表示有多少个单词。接下来
N
N
N行每行一个单词,单词中只包含小写字母。
输出格式:
输出
N
N
N个整数,每个整数占一行,第
i
i
i行的数字表示第
i
i
i个单词在文章中出现了多少次。
数据范围:
1
≤
N
≤
200
1≤N≤200
1≤N≤200
所有单词长度的总和不超过
1
0
6
10^6
106。
思路是AC自动机。可以这样想,对于每个串,我们把其所有的非空前缀都插到Trie里,那么问某个串 s s s出现的次数,其实就是问 s s s是Trie中多少个串的后缀。考虑将这个Trie建成AC自动机,那么每个节点 u u u的 n e [ u ] ne[u] ne[u]表示的是存在于Trie里的 u u u串的最长后缀(也就是说Trie里可能存储了好几个 u u u的后缀,存的最长的那个就是 n e [ u ] ne[u] ne[u]),那么所有以 s s s为后缀的串 u u u,一定满足 ∃ k , n e k [ u ] = s \exists k, ne^k[u]=s ∃k,nek[u]=s,也就是说 u u u从其 n e ne ne指针跳若干次一定会跳到Trie中 s s s的终点。而由于 n e ne ne指针一定是从深度深的点向深度浅的点指的,所以整个Trie的节点连同其所有 n e ne ne指针(当然除了树根的)形成一个有向无环图(事实上整个Trie的节点连同其所有 n e ne ne的反向指针形成一棵树),所以其可以拓扑排序,只需要从深的节点向上递推就可以了。不需要特地拓扑排序,只需要将BFS的顺序反序遍历递推即可。设 f [ u ] f[u] f[u]是 u u u这个串的出现次数(这里的 u u u取遍Trie里插入的所有串,即原来所有串的所有前缀),则 f [ u ] f[u] f[u]其实就是沿着 n e ne ne指针,有多少个点可以走到它,那么可以按照最后一步来分类,有: f [ u ] = 1 + ∣ { v : n e k [ v ] = u , k ≥ 1 } ∣ = 1 + ∑ n e [ v ] = u f [ v ] f[u]=1+|\{v:ne^k[v]=u,k\ge 1\}|=1+\sum_{ne[v]=u} f[v] f[u]=1+∣{v:nek[v]=u,k≥1}∣=1+ne[v]=u∑f[v]代码如下:
#include <iostream>
using namespace std;
const int N = 1e6 + 10;
int n;
int tr[N][26], f[N], idx;
int q[N], ne[N];
char s[N];
// id[i]指的是第i个串对应的是哪个Trie节点
int id[210];
void insert(int k) {
int p = 0;
for (int i = 0; s[i]; i++) {
int j = s[i] - 'a';
if (!tr[p][j]) tr[p][j] = ++idx;
p = tr[p][j];
// 要把这个串的所有前缀都插入到Trie里
f[p]++;
}
id[k] = p;
}
// 建AC自动机
void build() {
int hh = 0, tt = 0;
for (int i = 0; i < 26; i++)
if (tr[0][i]) q[tt++] = tr[0][i];
while (hh < tt) {
int t = q[hh++];
for (int i = 0; i < 26; i++) {
int &p = tr[t][i];
if (p) ne[p] = tr[ne[t]][i], q[tt++] = p;
else p = tr[ne[t]][i];
}
}
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%s", s);
insert(i);
}
build();
// 按ne指针的拓扑序递推,即按深度从深到浅递推,即按BFS逆序递推。
// BFS只遍历了除树根的点,一共点数是idx + 1,队列的最后一个元素下标是idx - 1
for (int i = idx - 1; i >= 0; i--) f[ne[q[i]]] += f[q[i]];
for (int i = 0; i < n; i++) printf("%d\n", f[id[i]]);
}
时空复杂度 O ( n ) O(n) O(n), n n n是所有单词总长度之和。