本来是早就该学的知识点了,但是拖了好长时间最近在重新捡起来。。。
【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 }
来源:oschina
链接:https://my.oschina.net/u/4380940/blog/3970003