>Link
ybtoj单词频率
>Description
一共有 n n n个单词,询问每个单词在所有单词中出现了几次(以子串的形式出现)
>解题思路
多个子串的出现次数,提醒我们用 AC自动机
但是之前做的都是多个子串在一个字符串中的出现次数,现在求的是多个子串在多个字符串中的出现次数,如何处理呢?
用
s
u
m
x
sum_x
sumx表示在
T
r
i
e
Trie
Trie树上,从根节点到
x
x
x组成的字符串,是多少个单词的前缀
用
a
n
s
x
ans_x
ansx表示在
T
r
i
e
Trie
Trie树上,从根节点到
x
x
x组成的字符串,是多少个单词的子串
那么我们可知:
- s u m x sum_x sumx一定算在 a n s x ans_x ansx内
- a n s x ans_x ansx一定算在 a n s n x t x ans_{nxt_x} ansnxtx内。因为 n x t x nxt_x nxtx代表的字符串是 x x x的一部分, x x x是某些单词的子串,那么 n x t x nxt_x nxtx也一定是这些单词的子串
其中
s
u
m
x
sum_x
sumx可以在我们建立
T
r
i
e
Trie
Trie树的过程中处理,一个单词经过的点的
s
u
m
sum
sum值都++
我们也同时发现,
n
x
t
x
nxt_x
nxtx的深度比
x
x
x浅,
a
n
s
ans
ans的转移是由深到浅的,所以我们要根据
B
F
S
BFS
BFS的逆序来处理
a
n
s
ans
ans
>代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define N 1000010
using namespace std;
queue<int> Q;
int n, t[N][30], cnt, p, num[N], sum[N], ans[N], nxt[N];
int q[N];
string a[N];
void build_trie ()
{
for (int i = 1; i <= n; i++)
{
cin >> a[i];
int len = a[i].size();
p = 0;
for (int j = 0; j < len; j++)
{
int x = a[i][j] - 'a' + 1;
if (!t[p][x]) t[p][x] = ++cnt;
p = t[p][x];
sum[p]++; //沿途记录
}
num[i] = p;
}
}
void get_fail ()
{
for (int i = 1; i <= 26; i++)
{
if (!t[0][i]) continue;
Q.push(t[0][i]);
nxt[t[0][i]] = 0;
}
while (!Q.empty())
{
int u = Q.front();
q[++q[0]] = u;
Q.pop();
for (int i = 1; i <= 26; i++)
{
if (!t[u][i]) t[u][i] = t[nxt[u]][i];
else
{
Q.push(t[u][i]);
nxt[t[u][i]] = t[nxt[u]][i];
}
}
}
}
int main()
{
scanf ("%d", &n);
build_trie ();
get_fail ();
for (int i = q[0]; i >= 1; i--)
ans[q[i]] = sum[q[i]];
for (int i = q[0]; i >= 1; i--)
ans[nxt[q[i]]] += ans[q[i]];
for (int i = 1; i <= n; i++)
printf ("%d\n", ans[num[i]]);
return 0;
}