题目链接:https://www.luogu.org/problem/P3808
AC自动机是看http://blog.c0per.org/2018-10/ac/和https://www.luogu.org/blog/juruohyfhaha/ac-zi-dong-ji看懂的,理解怎么操作不难,但是最主要要理解的是为什么这样子操作。
首先AC自动机是在字典树的基础上加了一些类似kmp的思想,但是kmp是相同前后缀,这里的失配指针是相同后缀,kmp用next指针进行跳转,AC自动机是用一个失配指针fail进行跳转。
构建失配指针的操作:
对于节点i,找它父亲节点的fail指针指向的那个节点j,看j的子节点中有没有和i节点相同的:
如果有,i节点的failz指针就指向j的那个子节点;
如果没有,继续找节点j的父亲节点的失配指针指向的节点循环
然后就是为什么要这么操作,构建完后的树是这样子的:
假如模式串是shey,以第3个节点为例:
当我们遍历到第3个节点时,到目前位置都匹配成功,并且节点3是第一个串的末尾,cntword[3]的值不为0,所以ans得到增加;然后就上跳到它的fail指针5,5是3的失配节点意味着,第二个串在第5个节点之前都是第一个串到第3个节点的后缀,也就是第二个串的he是第一个串she的后缀,那么she已知匹配,he就一定能匹配成功,但5不是第二个串的末尾,cntword[5]=0,ans没有变化;然后继续跳到5的失配节点7,第3个串到第7个节点也一定是匹配的,且7是第三个串的末尾,ans得到增加。
然后到了下一层,因为第3个节点实际上是没有y这个子结点的,但是因为它的失配指针指向的节点5有y这个子结点,所以我们在构建失配指针时,就已经将第3个节点的y子结点指向了节点6,同理这样子跳转过来本身就已经保证了节点6之前都是匹配成功的。
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
using namespace std;
const int maxn=1e6+10;
int cnt=0;
int trie[maxn][26];//trie[i][j]表示的当前字母为i+'a',且父节点编号为i的节点的编号
int cntword[maxn];
int fail[maxn];
void insert(string s)
{
int root=0;
for(int i=0;i<s.size();i++)
{
int next=s[i]-'a';
if(!trie[root][next])
trie[root][next]=++cnt;
root=trie[root][next];
}
cntword[root]++;//当前节点的单词数+1,而不是等于1,因为可能重复。
//这时的root是单词的末尾的编号,表示的是一整个单词,途中经历过的点的cntword是没有自增的;
}
void getFail()//层序遍历
{
queue<int>q;
for(int i=0;i<26;i++)
if(trie[0][i])//首先要先将最上面一层出现的字母(每个串的首字符)入队
{
fail[trie[0][i]]=0;
q.push(trie[0][i]);
}
while(!q.empty())
{
int now=q.front();
q.pop();
for(int i=0;i<26;i++)
{
if(trie[now][i])//通过枚举26个字母来找到now的子节点们
{
//该节点i的失败指针指向它父节点now的失败指针fail[now]的和i节点相同的节点
fail[trie[now][i]]=trie[fail[now]][i];
q.push(trie[now][i]);
}
else
trie[now][i]=trie[fail[now]][i];//如果不存在这个节点就上跳到失配节点的等于i的这个子节点上
}
}
}
int query(string s)
{
int now=0,ans=0;
for(int i=0;i<s.size();i++)
{
now=trie[now][s[i]-'a'];
for(int j=now;j&&cntword[j]!=-1;j=fail[j])//当前节点匹配完成或失配时就上跳fail指针
{
ans+=cntword[j];//只有j是某个单词末尾时才有值
cntword[j]=-1;//记录过的节点要标记,防止重复
}
}
return ans;
}
int main()
{
int n;
while(cin>>n)
{
cnt=0;
memset(cntword,0,sizeof(cntword));
memset(fail,0,sizeof(fail));
memset(trie,0,sizeof(trie));
string a,s;
for(int i=1;i<=n;i++)
{
cin>>a;
insert(a);
}
getFail();
cin>>s;
cout<<query(s)<<endl;
}
return 0;
}