题目大意:有n个单词,和一个长度为1e6的字符串,问这个字符串中出现过多少个单词
1<=n<=10000
思路:AC自动机模板题,先构建好字典树,然后构建失配指针,在匹配时沿着失配指针的方向走,在遍历的过程中累计出现过的单词数量,例如统计在字符串shers中出现过几个单词,我们先用he,her,hers,his,she即个单词构建字典树如下
首先从字典树的根开始,匹配第一个字符s,然后匹配第2个字符h,接着匹配第3个字符e
,匹配成功she,5号节点的失配指针指向2号节点,又匹配成功he,继续匹配第四个字符r,5号节点的r子节点指向其失配指针的r子节点,因此访问8号节点,继续匹配第5个字符s,匹配成功hers
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N = 1e6 + 5;
char s[N];
int tr[N][26], fail[N], num[N], cnt = 0;
void init()
{//初始化字典树
memset(tr[0], 0, sizeof tr[0]);
cnt = 0;
}
void insert(char s[])
{//建立字典树
int n = strlen(s + 1), root = 0;
for (int i = 1; i <= n; i++)
{
if (!tr[root][s[i] - 'a'])
{
tr[root][s[i] - 'a'] = ++cnt;
memset(tr[cnt], 0, sizeof tr[cnt]);
num[cnt] = 0;
}
root = tr[root][s[i] - 'a'];
}
num[root]++;//每个单词的末尾做标记
}
void getfail()
{//添加失配指针
queue<int>q;
for (int i = 0; i < 26; i++)
{
if (tr[0][i])
{
q.push(tr[0][i]);
fail[tr[0][i]] = 0;
}
}
while (!q.empty())
{
int now = q.front();
q.pop();
for (int i = 0; i < 26; i++)
{
if (tr[now][i])
{
fail[tr[now][i]] = tr[fail[now]][i];
q.push(tr[now][i]);
}
else
{
tr[now][i] = tr[fail[now]][i];
}
}
}
}
int find(char s[])
{//查找出现过多少个单词
int ans = 0, root = 0, len = strlen(s + 1);
for (int i = 1; i <= len; i++)
{
root = tr[root][s[i] - 'a'];
int tmp = root;
while (tmp)
{
ans += num[tmp];
num[tmp] = 0;
tmp = fail[tmp];
}
}
return ans;
}
int main()
{
int t;
cin >> t;
while (t--)
{
int n;
scanf("%d", &n);
init();
for (int i = 1; i <= n; i++)
{
scanf("%s", s + 1);
insert(s);
}
getfail();
scanf("%s", s + 1);
printf("%d\n", find(s));
}
return 0;
}