AC自动机(二次加强版)【AC自动机】【拓扑排序】

>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 1n2×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(STi)
那我们就在每次枚举的时候不一直搜到底,而是标记一下,然后最后再全部搜一遍直接统计
具体操作:把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;
}

Guess you like

Origin blog.csdn.net/qq_43010386/article/details/120901825