作用
AC自动机全称Aho-Corasick自动机,这个算法是在KMP和trie的基础上得到的更优秀的字符串匹配算法(所以强烈建议先参考KMP和Trie)。
实现
KMP是线性的匹配,AC自动机呢?不难想到在trie里匹配!
比如有she,her,hers,his,him这几个单词,我们需要在一个本文中查找这些单词。使用KMP需要每个单词先处理失配函数,然后还要遍历这个文本n次(n表示单词数量),能不能只遍历一次?这是有办法的:建立一棵trie,然后用trie来处理失配函数。
AC自动机查找和KMP类似,失配函数也非常类似,但是需要注意的是,失配函数的处理需要使用bfs拓展(也就是要一层一层来)。同时由于多个单词,在一个节点找到了单词,可能并不意味着没找到其他单词!所以我们还需要记录一个lst[i]表示离i节点最近的单词节点(但不能是i),这个在失配函数的构造中也可以处理出来。
用ΣL表示所有单词的总长度,n表示单词个数,len表示句子长度。那么n次KMP的复杂度就是O(ΣL+n*len),如果题目要求求出匹配总个数,那么复杂度可以被优化为O(ΣL+len)。
拓展
求出匹配总个数其实依靠的是lst的优化,即改变lst的定义,从“上一个位置”改为“前面的总个数”,如果题目要求输出所有方案,那么复杂度会退化为O(ΣL+匹配个数),在极端条件下复杂度和n次KMP的复杂度几乎一样。
模板
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=10000,maxl=50,maxT=maxn*maxl,maxL=1000000,maxi=26;
//=======================================================================================
struct Node
{
Node *son[maxi],*fa,*lst;
int w;
};
typedef Node* P_node;
Node tem[maxT+5];
P_node null=tem,P_tem=null,que[maxT+5];
P_node newNode(P_node son)
{
P_tem++;P_tem->fa=P_tem->lst=son;P_tem->w=0;
for (int i=0;i<maxi;i++) P_tem->son[i]=son;
return P_tem;
}
//=======================================================================================
int getid(char ch) {return ch-'a';}
struct AC //AC自动机结构体
{
P_node ro;
void clear() {ro=newNode(null);for (int i=0;i<maxi;i++) ro->son[i]=ro;ro->fa=ro->lst=ro;} //只要清空ro就可以了
void Insert(char *s) //普通trie插入
{
P_node now=ro;
for (int i=1;s[i];i++)
{
if (now->son[getid(s[i])]==ro) now->son[getid(s[i])]=newNode(ro);
//直接指向ro可以理解为指向null,因为ro实际上不会作为儿子
now=now->son[getid(s[i])];
}
now->w++;
}
void get_Fail()
{
int Head=0,Tail=0;
for (int i=0;i<maxi;i++)
if (ro->son[i]!=ro)
{
que[++Tail]=ro->son[i];
ro->son[i]->fa=ro->son[i]->lst=ro;
}
while (Head!=Tail)
{
P_node x=que[++Head];
for (int i=0;i<maxi;i++)
if (x->son[i]!=ro)
{
P_node now=x->son[i];now->fa=x->fa->son[i];
if (now->fa->w) now->lst=now->fa; else now->lst=now->fa->lst;
que[++Tail]=now;
} else x->son[i]=x->fa->son[i]; //这里有个细节:如果和KMP一样写while do推fa会很繁琐,所以就直接改成while do的终点
//2018.7.19UPD:其实这是记忆化,如果没有这句,处理一些操作的时候复杂度会出锅
}
}
int get_num(P_node now)
{
int num=0;
for (;now!=ro;now=now->lst) num+=now->w;
return num;
}
int Find(char *s)
{
P_node j=ro;int num=0;
for (int i=1;s[i];i++) j=j->son[getid(s[i])],num+=get_num(j);
return num;
}
};
//=======================================================================================
AC tr;
int te,n;
char now[maxL+5];
//=======================================================================================
bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
int reads(char* s)
{
int len=0;char ch=getchar();if (ch==EOF) return EOF;
s[++len]=ch;while (!Eoln(s[len])) s[++len]=getchar();s[len--]=0;
return 0;
}
void readln() {scanf("%*[^\n]");getchar();}
int main()
{
freopen("AC.in","r",stdin);
freopen("AC.out","w",stdout);
scanf("%d",&te);
while (te--)
{
scanf("%d",&n);readln();P_tem=null;tr.clear();
for (int i=1;i<=n;i++) reads(now),tr.Insert(now);tr.get_Fail();
reads(now);printf("%d\n",tr.Find(now));
}
return 0;
}