给定 个长度不超过 的由小写英文字母组成的单词准备查询,以及一篇长为 的文章,问:文中出现了多少个待查询的单词。多组数据。
输入格式
第一行一个整数 ,表示数据组数;
对于每组数据,第一行一个整数 ,接下去 行表示 个单词,最后一行输入一个字符串,表示文章。
输出格式
对于每组数据,输出一个数,表示文中出现了多少个待查询的单词。
样例
样例输入
1
5
she
he
say
shr
her
yasherhs
样例输出
3
AC自动机的匹配过程分两种情况:(1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;(2)当前字符不匹配(与kmp的过程类似),则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重复这2个过程中的任意一个,直到模式串走到结尾为止。
定义 f a i l [ i ] fail[i] fail[i]是与以 i i i节点为结尾的串的后缀有最大公共长度的前缀的结尾编号
简单来说就是没出现过的字母fail指向root,出现过的字母fail指向深度比它浅且离他最近的节点
听懂直接掌声!!!(下面可以不看)
下面咱们细说
首先root的fail指针指向0,然后root入队,进入循环。第1次循环的时候,我们需要处理2个节点:tree[u]c 和节点s。把这2个节点的失败指针指向root,并且先后进入队列,失败指针的指向对应图-2中的(1),(2)两条虚线;第2次进入循环后,从队列中先弹出h,接下来h节点的fail指针指向的节点,也就是root;fail[h]也就是0,这时退出循环,并把节点e的fail指针指向root,对应图-2中的(3),然后节点e进入队列;第3次循环时,现在s是队首,弹出的第一个节点a的操作与上一步操作的节点e相同,把a的fail指针指向root,对应图-2中的(4),并入队;第4次进入循环时,弹出节点h(图中左边那个),u是s代表的节点,s的fail指针指向root,由于root有h这个儿子节点,图中右边那个,这样便把左边那个h节点的失败指针指向右边那个root的儿子节点h,s是h的父亲节点,root是s的fail指针所指向的节点,右边的h是root的子节点对应图-2中的(5),然后h入队。,如果你现在弹出e(左e),想找一个r节点,但是此时并没有这个r节点,于是就顺着左e的fail指针,找到右e,右e有r的节点,于是可以找到。以此类推,讲完这个bfs就基本把AC自动机讲完了
if(tree[u][i])第一种情况
{
fail[tree[u][i]]=tree[fail[u]][i];
q.push(tree[u][i]);
}
else 第二种 只有失配时才可以
tree[u][i]=tree[fail[u]][i];
}
图片来源
并对其中注释做出修改
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2 * 1e6 + 9;
int tree[maxn][26];
int word[maxn];
int fail[maxn];
int tot = 0;
typedef long long ll;
void Inserts(string s)</