【洛谷P3808】【模板】AC自动机(简单版)

题目

题目链接:https://www.luogu.com.cn/problem/P3808
给定 \(n\) 个模式串 \(s_i\) 和一个文本串 \(t\),求有多少个不同的模式串在文本串里出现过。
两个模式串不同当且仅当他们编号不同。

思路

AC 自动机可以看做 KMP + Trie。
首先我们将所有模式串建成一棵 Trie 树,定义 \(fail[i]\) 表示一个点 \(i\) 到根路径形成的字符串,最长出现在 Trie 树中的后缀的位置。其定义可以类比 KMP 的 next 数组。
\(c[i][x]\) 表示:

  • 如果点 \(i\) 有字符 \(x\) 儿子,那么为这个儿子的编号。
  • 否则为点 \(i\) 到根形成的字符串最长拥有 \(x\) 儿子的后缀的 \(x\) 儿子编号。

显然 \(fail\) 会形成一个树形结构,我们可以构建出 fail 树,然后在文本串匹配到点 \(i\) 的时候不断往其 \(fail\) 跳,并将答案加上到达的点结尾的模式串数量。
注意要类比并查集路径压缩的方法处理 \(c\) 数组。
显然每个点只会遍历一遍,时间复杂度 \(O(\sum^{n}_{i=1}len_i)\)

代码

#include <bits/stdc++.h>
using namespace std;

const int N=1000010,M=27;
int n,len;
char s[N];

struct ACA
{
	int tot,c[N][M],num[N],fail[N];
	bool vis[N];
	
	void ins(char *ch)
	{
		int len=strlen(ch+1),p=0;
		for (int i=1;i<=len;i++)
		{
			int val=ch[i]-'a'+1;
			if (!c[p][val]) c[p][val]=++tot;
			p=c[p][val];
		}
		num[p]++;
	}
	
	void build()
	{
		queue<int> q;
		for (int i=1;i<=26;i++)
			if (c[0][i]) q.push(c[0][i]);
		while (q.size())
		{
			int u=q.front();
			q.pop();
			for (int i=1;i<=26;i++)
				if (c[u][i]) fail[c[u][i]]=c[fail[u]][i],q.push(c[u][i]);
					else c[u][i]=c[fail[u]][i];
		}
	}
	
	int query(char *ch)
	{
		int len=strlen(ch+1),p=0,ans=0;
		for (int i=1;i<=len;i++)
		{
			int val=ch[i]-'a'+1;
			p=c[p][val];
			for (int j=p;j && !vis[j];j=fail[j])
				ans+=num[j],vis[j]=1;
		}
		return ans;
	}
}AC;

int main()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
	{
		scanf("%s",s+1);
		AC.ins(s);
	}
	AC.build();
	scanf("%s",s+1);
	printf("%d",AC.query(s));
	return 0;
}
posted @ 2020-08-20 21:29  stoorz  阅读(115)  评论(0编辑  收藏  举报