正题
听说NOIP要考,所以临时补了一下,多了一种思考方式。
AC自动机是基于KMP和字典树的,要想透彻了解AC自动机,最好先学KMP和字典树。
那么,AC自动机是什么样子的呢?又是用来解决什么问题的呢?
【模板】AC自动机(简单版),可以以这题为例。
好像KMP是可以做的,试一下,发现时间复杂度会爆炸,因为文本串要遍历次。
通过磨题解,我们学习新算法。
AC自动机。
首先我们把n个模式串插入一个字典树中,接着,神奇的事情发生了。
我们对于字典树中的每一个节点,我们找其对应的fail指针。
fail指针指的是什么?从根到该节点的路径很明显构成一个字符串。
那么fail指针指向的就是这个字符串,在字典树中出现的最长的后缀。如图。
是不是很丑,因为fail指针很好理解。
接着怎么解决这一题呢?
先把AC自动机建出来,给每个字符串的结尾所对应的节点权值+1.
然后拿文本串上去跳,让它从根节点一直往下走(按照字符),如果一个节点没有对应的字符,就不断跳fail,直到跳到根。
如果当前节点是一个模式串的结尾,那么ans+这个节点的权值,并且给这个节点打一个标记,表示记录过了。(不计重复)
同时要加上fail指针的答案,因为满足这个点一定满足fail,fail指针是我的后缀啊。
#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
int n;
struct node{
int t,son[26],fail;
bool tf;
}s[1000010];
char ch[1000010];
int tot=0;
queue<int> f;
void insert(){
int l=strlen(ch+1);
int x=0;
for(int i=1;i<=l;i++){
int temp=ch[i]-'a';
if(s[x].son[temp]==-1){
s[x].son[temp]=++tot;
s[tot].t=0;memset(s[tot].son,-1,sizeof(s[tot].son));
}
x=s[x].son[temp];
}
s[x].t++;
}
void get_fail(){
f.push(0);
while(!f.empty()){
int x=f.front();f.pop();
for(int i=0;i<26;i++){
int y=s[x].son[i];
if(y==-1) continue;
int now=s[x].fail;
while(now!=0 && s[now].son[i]==-1) now=s[now].fail;
if(x!=0 && s[now].son[i]!=-1) s[y].fail=s[now].son[i];
f.push(y);
}
}
}
int get_ans(){
int ans=0;
int x=0,l=strlen(ch+1);
for(int i=1;i<=l;i++){
int temp=ch[i]-'a';
while(x!=0 && s[x].son[temp]==-1) x=s[x].fail;
if(s[x].son[temp]!=-1) x=s[x].son[temp];
int now=x;
while(now!=0 && !s[now].tf) ans+=s[now].t,s[now].tf=true,now=s[now].fail;
}
return ans;
}
int main(){
scanf("%d",&n);
memset(s[0].son,-1,sizeof(s[0].son));
for(int i=1;i<=n;i++) {
scanf("%s",ch+1);
insert();
}
scanf("%s",ch+1);
get_fail();
printf("%d",get_ans());
}