AC自动机——学习笔记

来源:互联网 发布:淘宝助理批量修改价格 编辑:程序博客网 时间:2024/06/16 01:39

什么是自动机

是一种数学模型,大概就是由一堆状态和状态转移规则等东西构成,能与外界交换信息,并改变动作。
这个是理论上的东西,了解就行,对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;} 
0 0
原创粉丝点击