AC自动机

Posted genius777

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AC自动机相关的知识,希望对你有一定的参考价值。

本来是早就该学的知识点了,但是拖了好长时间最近在重新捡起来。。。

 【AC自动机???自动AC机???】

刚学OI的时候,就听到学长说AC自动机,第一次听到这个名字还以为只是开玩笑说用来自动A题的BUG。。。(相信总会有人和我想法是一样的)

AC自动机就是字典树和KMP算法的结合,KMP实现的是单模匹配,也就是一个模式串与文本串进行匹配,但是如果我们有很多模式串怎么办呢?

这时我们就要引入字典树(发现树结构在OI学习中贼优越的样子)

字典树就是一棵可以表示所有出现单词的树,就像字典一样可以进行单词的查询!!!这样就可以实现多模匹配。

但是要是我们一个个跳着来匹配字符那就GG了,和单模匹配一样的暴力,所以我们就要用KMP的失配指针来优化,定义失配指针为fail

首先我们来看看字典树是怎么建立的(如:AAC,CAA,CAC,BA)

 

那么加上其失配指针后:

 

这就是将序列上的KMP变成了树上的AC自动机了 !!!

接下来我们来看看AC自动机怎么实现的。。。

首先我们要建立一棵字典树,Trie树:(这应该是基本操作,不会的就要看看字典树了)

1.存储结构

1 struct sd{
2     int son[30],end,fail;// 每个节点的下一个字符对应的节点,如果当前节点是一个单词的结尾那就记下来,失配指针
3 }trie[20005];
4 char txt[1000005],ch[155][75];//模式串,文本串
5 int vis[155],maxx,cnt,n;//每个单词的出现次数

2.建立字典树

 1 void insert(int v)
 2 {
 3     int len=strlen(ch[v]),node=0;//用来跳的指针
 4     for(int i=0;i<len;i++)
 5     {
 6         int b=ch[v][i]-\'a\';
 7         if(!trie[node].son[b])//如果没出现过那就新建一个节点
 8         trie[node].son[b]=++cnt;
 9         node=trie[node].son[b];
10     }
11     trie[node].end=v;
12 }

3.预处理fail指针

 1 void getfail()
 2 {
 3      queue<int> que;//队列来BFS
 4      que.push(0);
 5      while(!que.empty())
 6      {
 7          int v=que.front();que.pop();
 8          for(int i=0;i<26;i++)
 9          {
10              if(trie[v].son[i])
11              {
12                  int p=trie[v].fail;//如果有失配指针那么从失配指针开始配
13                  while(p!=-1)
14                  {
15                      if(trie[p].son[i])
16                      {trie[trie[v].son[i]].fail=trie[p].son[i];break;}//显然一个节点的失配指针的子节点也有这个节点的子节点,那就连上失配指针
17                      p=trie[p].fail;
18                  }
19                  if(p==-1) trie[trie[v].son[i]].fail=0;
20                  que.push(trie[v].son[i]);//压入队列
21              }
22          }
23      }
24 }

4.开始匹配

 1 void cmp()
 2 {
 3     int len=strlen(txt);
 4     int a=0;//匹配指针 
 5     for(int i=0;i<len;i++)
 6     {
 7         int b=txt[i]-\'a\';
 8         while(a&&!trie[a].son[b]) a=trie[a].fail;//如果失配了,那就跳到失配的地方去 
 9         if(trie[a].son[b]) a=trie[a].son[b];
10         int p=a;
11         while(p) //在匹配指针的地方匹配一下 
12         {
13             if(trie[p].end) //如果有单词的结尾说明已经在字典树上匹配到了一个单词 
14             {
15                 vis[trie[p].end]++;//这个单词就多出现了一次 
16                 maxx=max(maxx,vis[trie[p].end]);//取个极值 
17             }
18             p=trie[p].fail;
19         }
20     }
21 }

可以看看一道模板题

 

【代码实现】

 

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<queue>
 4 using namespace std;
 5 struct sd{
 6     int son[30],end,fail,cot;
 7 }trie[20005];
 8 char txt[1000005],ch[155][75];
 9 int vis[155],maxx,cnt,n;
10 void clear()
11 {
12     memset(trie,0,sizeof(trie));
13     memset(vis,0,sizeof(vis));
14     cnt=0,maxx=0,trie[0].fail=-1;
15 }
16 void insert(int v)
17 {
18     int len=strlen(ch[v]),node=0;
19     for(int i=0;i<len;i++)
20     {
21         int b=ch[v][i]-\'a\';
22         if(!trie[node].son[b])
23         trie[node].son[b]=++cnt;
24         node=trie[node].son[b];
25     }
26     trie[node].end=v;
27 }
28 void getfail()
29 {
30     queue<int> que;
31     que.push(0);
32     while(!que.empty())
33     {
34         int v=que.front();que.pop();
35         for(int i=0;i<26;i++)
36         {
37             if(trie[v].son[i])
38             {
39                 int p=trie[v].fail;
40                 while(p!=-1)
41                 {
42                     if(trie[p].son[i])
43                     {trie[trie[v].son[i]].fail=trie[p].son[i];break;}
44                     p=trie[p].fail;
45                 }
46                 if(p==-1) trie[trie[v].son[i]].fail=0;
47                 que.push(trie[v].son[i]);
48             }
49         }
50     }
51 }
52 void cmp()
53 {
54     int len=strlen(txt);
55     int a=0;
56     for(int i=0;i<len;i++)
57     {
58         int b=txt[i]-\'a\';
59         while(a&&!trie[a].son[b]) a=trie[a].fail;
60         if(trie[a].son[b]) a=trie[a].son[b];
61         int p=a;
62         while(p)
63         {
64             if(trie[p].end)
65             {
66                 vis[trie[p].end]++;
67                 maxx=max(maxx,vis[trie[p].end]);
68             }
69             p=trie[p].fail;
70         }
71     }
72 }
73 int main()
74 {
75     while(scanf("%d",&n))
76     {
77         if(!n) break;
78         clear();
79         for(int i=1;i<=n;i++)
80         scanf("%s",ch[i]),insert(i);
81         getfail();
82         scanf("%s",txt);
83         cmp();
84         printf("%d\\n",maxx);
85         for(int i=1;i<=n;i++)
86         if(vis[i]==maxx) printf("%s\\n",ch[i]);
87     }
88     return 0;
89 }

 

以上是关于AC自动机的主要内容,如果未能解决你的问题,请参考以下文章

POJ3691DNA repair(AC自动机,DP)

HDU4057 Rescue the Rabbit(AC自动机+状压DP)

Codeforces 86C Genetic engineering(AC自动机+DP)

POJ1699 Best Sequence(AC自动机+状压DP)

POJ - 2778 ~ HDU - 2243 AC自动机+矩阵快速幂

HDU2457 DNA repair(AC自动机+DP)