>Link
luogu P5357
>Description
给你一个文本串 S S S 和 n n n个模式串 T i T_i Ti,求每个 T i T_i Ti 在 S S S 中出现的次数
1 ≤ n ≤ 2 × 1 0 5 1≤n≤2×10^ 5 1≤n≤2×105, T 1.. n T_{1..n} T1..n 的长度总和不超过 2 × 1 0 5 2×10^5 2×105 , S S S 的长度不超过 2 × 1 0 6 2×10^6 2×106
>解题思路
其实可以发现这道题跟AC自动机(加强版)的操作差不多,但是直接用加强版的方法交上去会TLE,因为数据变大了(废话
然后我们观察一下,发现这种方法的弊端就是文本串匹配的时候,每枚举到一个位置,就要在trie树上沿fail指针一直搜到底,因为这样才能把所有出现了的子串都标记一下(不同于简单版的只要标记了一次就不用标记了),那这样的时间复杂度就大概为 O ( ∣ S ∣ ∗ ∑ ∣ T i ∣ ) O(|S|*\sum|T_i|) O(∣S∣∗∑∣Ti∣)了
那我们就在每次枚举的时候不一直搜到底,而是标记一下,然后最后再全部搜一遍直接统计
具体操作:把fail指针看成一条边,那我们原来的操作就是把一条链上把某点之后的所有点+1,现在我们就只在这个点上标记一个1,全部标记完后,按照拓扑序把fail指针组成的图搜一遍(从入度为0的开始搜),后面的点累加上前面的点的值就行了
>代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#define N 1000010
using namespace std;
queue<int> Q;
int n, ed[N], t[N][30], fail[N], tot, flag[N], sum[N], r[N];
string s, a[N];
void build_trie ()
{
int len, now, x;
for (int i = 1; i <= n; i++)
{
len = a[i].size();
a[i] = " " + a[i];
now = 0;
for (int j = 1; j <= len; j++)
{
x = a[i][j] - 'a' + 1;
if (!t[now][x]) t[now][x] = ++tot;
now = t[now][x];
}
flag[i] = now;
}
}
void get_fail ()
{
for (int i = 1; i <= 26; i++)
if (t[0][i]) Q.push (t[0][i]);
while (!Q.empty())
{
int u = Q.front();
Q.pop();
r[fail[u]]++;
for (int i = 1; i <= 26; i++)
if (!t[u][i]) t[u][i] = t[fail[u]][i];
else
{
Q.push (t[u][i]);
fail[t[u][i]] = t[fail[u]][i];
}
}
}
void ACauto ()
{
int len = s.size();
s = " " + s;
int now = 0, x;
for (int i = 1; i <= len; i++)
{
x = s[i] - 'a' + 1;
now = t[now][x];
sum[now]++;
}
}
int main()
{
scanf ("%d", &n);
for (int i = 1; i <= n; i++) cin >> a[i];
cin >> s;
build_trie ();
get_fail ();
ACauto ();
for (int i = 1; i <= tot; i++)
if (!r[i]) Q.push (i);
while (!Q.empty())
{
int u = Q.front();
Q.pop();
if (!fail[u]) continue;
sum[fail[u]] += sum[u];
r[fail[u]]--;
if (!r[fail[u]]) Q.push (fail[u]);
}
for (int i = 1; i <= n; i++)
printf ("%d\n", sum[flag[i]]);
return 0;
}