网址: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代码: