网址:https://www.luogu.org/problem/P3808

题意:

给定$n$个模式串和$1$个文本串,求有多少个模式串在文本串里出现过,相同的模式串需要多次计算。

题解:

$AC$自动机的模板题。$AC$自动机是基于$Trie$树的有限状态自动机,又称$Trie$图,$AC$自动机的结点的$fail$指针构造方法如下:在$Trie$树上,类似于$KMP$的$fail$指针,对于非空结点,找到其父亲的$fail$指针对应的结点的相同字母的儿子结点。对于空节点,直接把它连到其父亲的$fail$指针对应的结点的相同字母的儿子结点,根结点的儿子的$fail$指针指向根,形成转移图。然后查询的时候,我们要求的是求出现次数,所以我们当到达一个结点的时候,我们可以跳$fail$指针匹配其后缀,然后匹配过的加上标记防止重新匹配。

#include <iostream>
#include <string>
#include <queue>
using namespace std;
#define maxn int(2e6+5)
int trie[maxn][26];
int cntword[maxn];
int fail[maxn];
int cnt = 0;
struct ac
{
	void insert(string& str)//插入字符串
	{
		int root = 0, next;
		for (int i = 0; i < str.size(); ++i)
		{
			next = str[i] - 'a';
			if (!trie[root][next])
				trie[root][next] = ++cnt;//将字符的位置按照放入顺序递增
			root = trie[root][next];
		}
		++cntword[root];//该位置单词结束
	}
	void buildfail()
	{
		queue<int>que;
		for (int i = 0; i < 26; ++i)
		{
			if (trie[0][i])
			{
				fail[trie[0][i]] = 0;//根的儿子的fail一定指向根
				que.push(trie[0][i]);//将其放入队列
			}
		}
		while (!que.empty())
		{
			int now = que.front();//出队,以其为根
			que.pop();
			for (int i = 0; i < 26; ++i)
			{
				if (trie[now][i])
				{
					fail[trie[now][i]] = trie[fail[now]][i];//fail指向其父节点的fail对应的相同子节点
					que.push(trie[now][i]);
				}
				else
					trie[now][i] = trie[fail[now]][i];//空节点是其父节点对应fail的相同儿子
			}
		}
	}
	int query(string& str)
	{
		int now = 0, ans = 0;
		for (int i = 0; i < str.size(); ++i)
		{
			now = trie[now][str[i] - 'a'];
			for (int j = now; j && cntword[j] != -1; j = fail[j])//匹配根到当前结点的子串的后缀以节省时间
			{
				ans += cntword[j];
				cntword[j] = -1;
			}
		}
		return ans;
	}
};
int main()
{
	string tmp;
	ios::sync_with_stdio(0);
	cin.tie(0);
	ac AC;
	int n;
	cin >> n;
	for (int i = 0; i < n; ++i)
	{
		cin >> tmp;
		AC.insert(tmp);
	}
	fail[0] = 0;
	cin >> tmp;
	AC.buildfail();
	cout << AC.query(tmp) << endl;
	return 0;
}

  

AC代码:

02-10 23:37