[AC自动机]入门
我不是柳橙汁
2017-12-25 21:39:07
[原博客链接](http://www.cnblogs.com/cjyyb/p/7196308.html)
AC自动机
一直想写AC自动机了
但是考虑到学习AC自动机之前
还需要一点其他的知识的基础
于是我先补充好了Trie树和KMP的blog
如果以上两个知识点没有学好的话
请先学习这两个知识点再来学习AC自动机
[Trie(字典树)](http://blog.csdn.net/qq\_30974369/article/details/74936845)
[KMP算法](http://blog.csdn.net/qq\_30974369/article/details/74276186)
如果能够解决上面的两个 算法/结构 那么,
欢迎继续学习AC自动机
首先我们要知道AC自动机是干什么用的。
大家都知道KMP算法是求单字符串对单字符串的匹配使用的
(因为我默认你们上面的两个东西都学好了)
那么,多个字符串在一个字符串上的匹配怎么办?
***
举例子永远是最好的
求 abab 在 abababbabbaabbabbab 中出现了几次
很明显,求出abab的next数组,然后进行KMP的匹配即可出解。
求 aba aca bab sab sba 在字符串 asabbasbaabbabbacaacbscbs 中总共出现了几次。
嗯嗯嗯。。。
这个要怎么办?
每次对一个需要匹配的串求一次next数组,然后一次次去匹配?
显然,这样变得很慢很慢了。。。
如果需要匹配的串很多很多的话。。。。。
不敢想象。。,,
那么,我们要如何解决这类问题呢?
恩,AC自动机。
常规而言,看看AC自动机是啥玩意
以下来自某度某科:
Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。
好吧,这个说了跟没说似的(就象征意义的看一下吧)
正式开始,我们来讲解AC自动机
AC自动机需要提前知道所有的需要匹配的字符串
例如
say she shr her
那么第一步
把它们构建成一棵Trie树
![](http://img.blog.csdn.net/20170711214734750?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzA5NzQzNjk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
灰色的结点代表一个单词的结尾
第一步应该是最简单的一步之一
只需要构建一棵Trie树即可
(再说一遍,如果前面两个东西没学好,先去学习完再继续学习AC自动机)
接着是第二步,也就是最重要的一步。
构建失配指针
这一步很KMP算法中的next数组是类似的,通过跳转来省略重复的检查。
那么要如何构建呢?
我先把构建好的放出来。
![](http://img.blog.csdn.net/20170711221050491?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzA5NzQzNjk=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
恩恩
我知道我画的图很丑很丑很丑
并不要在意那些奇怪的颜色问题
虽然画在了这里,,,,但是
并不知道怎么求对不对。。
我们先看看这个指针是要干什么吧。
每次沿着Trie树匹配,匹配到当前位置没有匹配上时,直接跳转到失配指针所指向的位置继续进行匹配。
所以,这个Trie树的失配指针要怎么求?
dalao们的blog告诉我们
Trie树的失配指针是指向:沿着其父节点 的 失配指针,一直向上,直到找到拥有当前这个字母的子节点 的节点 的那个子节点
感觉听起来很复杂吧。。。。
我也是这么觉得的
但是
自己画一下图就好了
值得一提的是,第二层的所有节点的失配指针,都要直接指向Trie树的根节点。
我也觉得我自己讲的很复杂。。。。。
怎么说呢,求失配指针是一个BFS的过程
需要逐层扩展(因为要用到父节点的失配指针)
所以,觉得每一次求失配指针都需要沿着之前的失配指针走一遍?
显然并不需要
那么怎么办?
这里看代码,自己理解一下(画图就是学习AC自动机的诀窍)
```cpp
void Get_fail(){//构造fail指针
queue<int> Q;//队列
for(int i=0;i<26;++i){//第二层的fail指针提前处理一下
if(AC[0].vis[i]!=0){
AC[AC[0].vis[i]].fail=0;//指向根节点
Q.push(AC[0].vis[i]);//压入队列
}
}
while(!Q.empty()){//BFS求fail指针
int u=Q.front();
Q.pop();
for(int i=0;i<26;++i){//枚举所有子节点
if(AC[u].vis[i]!=0){//存在这个子节点
AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i];
//子节点的fail指针指向当前节点的
//fail指针所指向的节点的相同子节点
Q.push(AC[u].vis[i]);//压入队列
}
else AC[u].vis[i]=AC[AC[u].fail].vis[i];//不存在这个子节点
//当前节点的这个子节点指向当前节点fail指针的这个子节点
}
}
}
```
如果你仔细的画画图
就会发现上面是一种很巧妙的构建方式
并不需要沿着失配指针向上移动。
嗷。。。。
失配指针写完了。。。
最后直接写匹配???
这个我觉得没有必要贴代码
直接讲述一下即可
首先,指针指向根节点
依次读入单词,检查是否存在这个子节点
然后指针跳转到子节点
如果不存在
直接跳转到失配指针即可
***
AC自动机差不多就到这里
三个模板题我放一下链接,大家可以自己查阅一下完整代码
[【洛谷3808】](http://blog.csdn.net/qq\_30974369/article/details/74557224)
[【洛谷3796】](http://blog.csdn.net/qq\_30974369/article/details/74907185)
[【CJOJ1435】](http://blog.csdn.net/qq\_30974369/article/details/74567503)