什么是自动机
是一种数学模型,大概就是由一堆状态和状态转移规则等东西构成,能与外界交换信息,并改变动作。
这个是理论上的东西,了解就行,对AC自动机的理解没有大影响。
什么是AC自动机?
通俗的讲就是在Trie上做kmp,处理多模式串匹配问题。
Trie 的每个结点就是一个状态,根结点是初始状态。
AC自动机的行为被定义为一下3个函数:
1. next函数 ch(q,a):返回从当前状态q走值为a的边后所到达的状态。如果根节点没有值为a的边,那么ch(0,a)=0,(就是说:当读入不匹配的字符时,自动机保持在初始状态)
如果从结点q出发有一条边值为a到达结点v,那么 ch(q,a)=v,否则ch(q,a)=Φ。
2. fail函数 fail(q):返回从状态q(q!=root)匹配错误时要转移到的状态
记L(q)表示Trie上q节点对于的字符串,
如果L(v)是L(q)的一个后缀,且是最长的后缀,则fail(q)=v(kmp的思想)。
fail(q)总是能返回一个状态,因为L(root)=Φ是任何模式串的前缀
3. output函数out(q):输出在状态q时,所有匹配的模式串。
上面的描述比较形式化。简单来说:
先把所有待匹配串插入到一颗Trie中。
对于每个节点,我们需要额外计算一些信息:
fail指针:如上所述,类似kmp中的next表。
cnt : 若这个位置是某个模式串的结尾,就cnt++。
last指针:指向沿着当前节点的fail指针往回走,第一个遇到的cnt>0的节点。是用于output的。
匹配
假设我们已经构造出了这些信息。考虑如何进行匹配,大概流程是这样的:
根据目标串一个一个字符在Trie上走,
每走到一个节点,执行一次output(顺着last指针往后累加答案)。走到下个节点时,如果ch(p,s[i])不为空则直接走过去,否则沿着fail指针走,直到匹配成功或者到初始状态(还是kmp)
注意到“沿着fail指针走,直到匹配成功或者到初始状态”这个过程较繁琐,而且只有ch(p,s[i])为空时才会执行,就想到可以优化一下,直接把ch(p,s[i])指到要跳到的节点。
代码如下:
int Find(P_node p){ // 即output
int res=0;
for(;p!=root;p=p->last) res+=p->cnt, p->cnt=0;
return res;
}
int Query(char* s){ // 匹配
int res=0, len=strlen(s);
P_node p=root;
for(int i=0;i<=len-1;i++) p=p->ch[s[i]-'a'], res+=Find(p);
return res;
}
构造
回到如何构造这些信息,显然都可以一次对Trie的bfs遍历实现,具体对于p的某个儿子v:
v->fail=p->fail->ch[i];
v->last=(v->fail->cnt?v->fail:v->fail->last);
要注意的是root的直接儿子的fail和last都指向root。
理解定义后还是比较显然的。
模板题PDU2222代码:
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
struct node{
int cnt; node *fail,*last;
node* ch[27];
} base[500000], *len, nil, *null=&nil, *root;
typedef node* P_node;
P_node newnode(node* son=NULL){
len->cnt=0; len->fail=son; for(int i=0;i<=25;i++) len->ch[i]=son;
return len++;
}
void Insert(P_node &p,char *now){
if(p==null) p=newnode(null);
if((*now)=='\000'){ p->cnt++; return; }
Insert(p->ch[(*now)-'a'],now+1);
}
queue<P_node> que;
void build(){
root->fail=root->last=root;
for(int i=0;i<=25;i++) if(root->ch[i]!=null){
P_node v=root->ch[i]; que.push(v);
v->fail=v->last=root;
} else root->ch[i]=root;
while(!que.empty()){
P_node p=que.front(); que.pop();
for(int i=0;i<=25;i++) if(p->ch[i]!=null){
P_node v=p->ch[i]; que.push(v);
v->fail=p->fail->ch[i];
v->last=(v->fail->cnt?v->fail:v->fail->last);
} else p->ch[i]=p->fail->ch[i];
}
}
int Find(P_node p){
int res=0;
for(;p!=root;p=p->last) res+=p->cnt, p->cnt=0;
return res;
}
int Query(char* s){
int res=0, len=strlen(s);
P_node p=root;
for(int i=0;i<=len-1;i++) p=p->ch[s[i]-'a'], res+=Find(p);
return res;
}
int _test,n,m;
char s[1000005];
int main(){
freopen("ac.in","r",stdin);
freopen("ac.out","w",stdout);
scanf("%d",&_test);
while(_test--){
len=base; root=newnode(null);
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%s",s), Insert(root,s);
build();
scanf("%s",s); printf("%d\n",Query(s));
}
return 0;
}