AC自动机

AC自动机,全称Aho-Corasick自动机。如果没记错的话好像就是前缀自动机。

其实AC自动机就是KMP上树的产物。理解了KMP,那AC自动机应该也是很好理解的。

与KMP类似,AC自动机也是扔一个字符走一步。当前状态始终只有一个,每次如何走都是确定的,换句话说AC自动机是一种确定型有限状态自动机(DFA)。

进行模式匹配是AC自动机的基本应用。如果稍加拓展一下,就可以知道在AC自动机上走k步就相当于产生了一个长为k、只包含给定字符集的字符串。借助这个性质,可以在AC自动机上DP来解决一些字符串有关问题。

注意一般应用的时候都会把节点不存在的儿子指向跳fail之后的结果,这样就可以在匹配时省掉fail,代码简洁的同时还能提速。

来点例题:

1. COGS1913 AC自动机

一道AC自动机模板题,没啥好说的。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=10010;
 6 void insert(const char*,int);
 7 void getfail();
 8 void match();
 9 void inc(int);
10 int n,ch[maxn][26]={{0}},val[maxn]={0},fail[maxn]={0},last[maxn]={0},q[maxn],head=0,tail=0,cnt=0,a[110];
11 char s[110][60],c;
12 int main(){
13 #define MINE
14 #ifdef MINE
15     freopen("ACautomata.in","r",stdin);
16     freopen("ACautomata.out","w",stdout);
17 #endif
18     scanf("%d",&n);
19     for(int i=1;i<=n;i++){
20         scanf("%s",s[i]);
21         insert(s[i],i);
22     }
23     getfail();
24     match();
25     for(int i=1;i<=n;i++)printf("%s %d\n",s[i],a[i]);
26 #ifndef MINE
27     printf("\n-------------------------DONE-------------------------\n");
28     for(;;);
29 #endif
30     return 0;
31 }
32 void insert(const char *c,int i){
33     int x=0;
34     while(*c){
35         if(!ch[x][*c-'a'])ch[x][*c-'a']=++cnt;
36         x=ch[x][*c-'a'];
37         c++;
38     }
39     val[x]=i;
40 }
41 void getfail(){
42     int x,y;
43     for(int c=0;c<26;c++)if(ch[0][c])q[tail++]=ch[0][c];
44     while(head!=tail){
45         x=q[head++];
46         for(int c=0;c<26;c++){
47             if(ch[x][c]){
48                 q[tail++]=ch[x][c];
49                 y=fail[x];
50                 while(y&&!ch[y][c])y=fail[y];
51                 fail[ch[x][c]]=ch[y][c];
52                 last[ch[x][c]]=val[fail[ch[x][c]]]?fail[ch[x][c]]:last[fail[ch[x][c]]];
53             }
54             else ch[x][c]=ch[fail[x]][c];
55         }
56     }
57 }
58 void match(){
59     char c;
60     int x=0;
61     for(;;){
62         do c=getchar();while((c<'a'||c>'z')&&c!=EOF);
63         if(c==EOF)return;
64         x=ch[x][c-'a'];
65         if(val[x])inc(x);
66         else if(last[x])inc(last[x]);
67     }
68 }
69 void inc(int x){
70     if(!x)return;
71     a[val[x]]++;
72     inc(last[x]);
73 }
View Code

2. POI2000 病毒

对模式串建AC自动机,问题就转化为了在AC自动机上是否存在一个不经过任何危险节点(单词节点或者存在last的节点)且无限长的路径。换句话说,就是问AC自动机上有没有环。

然后直接上dfs找环即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=30010;
void insert(const char*,int=0);
void init();
void dfs(int);
int n,ch[maxn][2]={{0}},val[maxn]={0},fail[maxn]={0},last[maxn]={0},q[maxn],cnt=0,head=0,tail=0;
bool vis[maxn]={false},ins[maxn]={false},ok=false;
char c[maxn];
int main(){
#define MINE
#ifdef MINE
    freopen("wir.in","r",stdin);
    freopen("wir.out","w",stdout);
#endif
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%s",c);
        insert(c);
    }
    init();
    dfs(0);
    if(!n||ok)printf("TAK");
    else printf("NIE");
#ifndef MINE
    printf("\n-------------------------DONE-------------------------\n");
    for(;;);
#endif
    return 0;
}
void insert(const char* c,int x){
    while(*c){
        if(!ch[x][*c-'0'])ch[x][*c-'0']=++cnt;
        x=ch[x][*c-'0'];
        c++;
    }
    val[x]++;
}
void init(){
    int x,y;
    for(int c=0;c<2;c++)if(ch[0][c])q[tail++]=ch[0][c];
    while(head!=tail){
        x=q[head++];
        for(int c=0;c<2;c++){
            if(ch[x][c]){
                q[tail++]=ch[x][c];
                y=fail[x];
                while(y&&!ch[y][c])y=fail[y];
                fail[ch[x][c]]=ch[y][c];
                last[ch[x][c]]=val[ch[y][c]]?ch[y][c]:last[ch[y][c]];
            }
            else ch[x][c]=ch[fail[x]][c];
        }
    }
}
void dfs(int x){
    if(val[x]||last[x])return;
    if(ins[x]){
        ok=true;
        return;
    }
    if(vis[x])return;
    vis[x]=ins[x]=true;
    for(int c=0;c<2;c++){
        if(ch[x][c]){
            dfs(ch[x][c]);
            if(ok)return;
        }
    }
    ins[x]=false;
}
View Code

3. COGS2248 情书

问你是否n个模式串都在文本串中出现过,AC自动机模板题。

话说string瞎搞居然比AC自动机快……

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=100010;
 6 void insert(const char*,int);
 7 void getfail();
 8 void match(const char*);
 9 void inc(int);
10 int n,ch[maxn][26]={{0}},fail[maxn]={0},last[maxn]={0},val[maxn]={0},q[maxn],head=0,tail=0,cnt=0,a[110];
11 char s[1350010],c;
12 bool ok;
13 int main(){
14 #define MINE
15 #ifdef MINE
16     freopen("lettera.in","r",stdin);
17     freopen("lettera.out","w",stdout);
18 #endif
19     scanf("%d",&n);
20     for(int i=1;i<=n;i++){
21         scanf("%*[^a-z]");
22         scanf("%[a-z]",s);
23         insert(s,i);
24     }
25     getfail();
26     for(;;){
27         scanf("%*[^a-z]");
28         if(scanf("%[a-z]",s)!=1)break;
29         fill_n(a+1,n,0);
30         match(s);
31         ok=true;
32         for(int i=1;i<=n;i++)if(!a[i]){
33             ok=false;
34             break;
35         }
36         printf(ok?"Yes\n":"No\n");
37     }
38 #ifndef MINE
39     printf("\n-------------------------DONE-------------------------\n");
40     for(;;);
41 #endif
42     return 0;
43 }
44 void insert(const char *c,int i){
45     int x=0;
46     while(*c){
47         if(!ch[x][*c-'a'])ch[x][*c-'a']=++cnt;
48         x=ch[x][*c-'a'];
49         c++;
50     }
51     val[x]=i;
52 }
53 void getfail(){
54     int x,y;
55     for(int c=0;c<26;c++)if(ch[0][c])q[tail++]=ch[0][c];
56     while(head!=tail){
57         x=q[head++];
58         for(int c=0;c<26;c++){
59             if(ch[x][c]){
60                 q[tail++]=ch[x][c];
61                 y=fail[x];
62                 while(y&&!ch[y][c])y=fail[y];
63                 fail[ch[x][c]]=ch[y][c];
64                 last[ch[x][c]]=val[fail[ch[x][c]]]?fail[ch[x][c]]:last[fail[ch[x][c]]];
65             }
66             else ch[x][c]=ch[fail[x]][c];
67         }
68     }
69 }
70 void match(const char *c){
71     int x=0;
72     while(*c&&*c!='$'){
73         x=ch[x][*c-'a'];
74         if(val[x])inc(x);
75         else if(last[x])inc(last[x]);
76         c++;
77     }
78 }
79 void inc(int x){
80     if(!x)return;
81     a[val[x]]++;
82     inc(last[x]);
83 }
View Code

其实例题还有很多,只不过限于时间(zi ji tai cai)没做,在此贴上大概思路:

1. [SCOI 2012] 喵星球上的点名

大概思路肯定是建AC自动机然后模式匹配。但是有一个问题,模式串太多,暴力匹配肯定会T。

考虑每个节点的last,显然它们构成了一棵树。因此可以对last树做树上差分,最后一遍dfs求出每个模式串出现的次数。至于每次有多少模式串出现,直接用节点的深度就行了。

2. 文本生成器(08年俞华程论文题)

感觉并不用减法原理就可以写……(不过不一定正确,如果有发现不对的话请告诉我)

先建AC自动机,然后就是一个AC自动机上的DP。

令f[i][j][0/1]为从i出发再走k步的方案数,第三维0表示没有出现过模式串,1表示出现过了。

转移方程:

f[i][j][0]=sum{f[ch[i][c]][j-1][0]}

f[i][j][1]=sum{f[ch[i][c]][j-1][1]+f[ch[i][c]][j-1][0]}

边界:

f[0][0][0]=1,其余j=0情况均为0。

然后,显然这是一个线性递推式,而状态又不多,因此可以用矩阵快速幂优化。构造一个转移矩阵,然后乘乘乘就好了。

 

尽头和开端,总有一个在等你。

posted @ 2016-11-02 19:08  AntiLeaf  阅读(188)  评论(2编辑  收藏  举报