浅谈AC自动机
写在前面:从10月23日开始写这篇博文,离NOIP2018只有十多天了。坚持不停课的倔强蒟蒻(我)尽量每天挤时间多搞一搞信竞(然而还要准备期中考试)。NOIP争取考一个好成绩吧。
一、简介
AC自动机,全称Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。(并不是让你的代码自动AC的算法)
AC自动机是必须建立在KMP和Trie的基础上的。所以如果你不会KMP或Trie,你可以给这篇文章点赞然后去看:
我们知道,KMP算法是处理一个字符串(模式串)对应一段文本(主串)的,而AC自动机是用来处理多个字符串和一段文本的匹配问题的
举个例子:
有N个单词,平均长度为L;文章长度为M。求这些单词在文中出现了多少次?(一个单词出现多次算多次)
Algorithm1:暴力。时间复杂度O(NML)
Algorithm2:AC自动机。时间复杂度O((N+M)L)
二、步骤
简单来说分为三步:
1、将所有的单词(模式串)构建一棵Trie
2、构造每一个结点的nxt数组
3、利用前缀指针对文本进行匹配
这里的前缀指针是算法的重点,与KMP中的next数组十分相似。所以AC自动机可以看作是在Trie上的KMP。
三、详细流程
(一)将所有的单词(模式串)构建一棵Trie
这个Trie跟普通的Trie没有任何区别,不做讲解了
注意一点,如果本题的要求是一个单词出现多次算做多次的话,那么Trie中的book数组每遍要+1而不是=1
(二)构造每一个结点的nxt数组
这个重点讲解一下
首先假设我们的Trie长这个样子:

需要匹配的文本是"abb"
我们前两个字符都能匹配:

我们走到了4号结点。
然后我们发现下一个字符是'b',然而四号节点的儿子中没有4!
那该怎么办呢?
要返回根节点并从'b'继续搜索吗?
不能这么做!因为如果这样,前两个字符'ab'不就浪费掉了吗?而我们的目的是找到全部的单词啊。
所以就会想到,对于前两个字符,我们要尽可能的保留
“尽可能地保留”换成严谨的语言就是满足最长后缀关系
即:在Trie上另找一个结点,使它所代表的字符串与当前节点所代表的字符串拥有最长的公共后缀
在上面的例子中,我们找到3号节点。它所代表的字符串是'b',与当前字符串'ab'具有最长的公共后缀
那就是它了!

继续查找。文本中的第三个字符是'b',而3号结点的儿子中有'b',还恰好有book标记。
那么我们就找到了一个单词'bb'!
思考上面的过程,有没有觉得上图中的那个红色箭头非常重要?在我们匹配失败时,它可以告诉我们一个新的结点并继续匹配。
我们还发现,每个结点的“失配箭头”都是固定的,只与模式串有关,与主串(文本)无关!
那么,完全可以预处理出一个数组,将每个结点的“失配箭头”储存下来!
艾玛……厉害了
这个数组,就是AC自动机中的核心,nxt数组!
nxt[i]=j表示当匹配到i无法匹配时,j所代表的字符串与i所代表的字符串拥有最长公共后缀,所以接下来就要到j继续匹配
说的这么多,终于把nxt数组是什么以及它的用途说完了……不知道说清楚没有
下面是某个Trie的nxt数组建立过程(图片来自于《信息学奥赛一本通.提高篇》)


换成代码就是这样的:
for(i=;i<;i++)
if(trie[][i])
Q.push(trie[][i]);
while(!Q.empty())
{
int u=Q.front();
Q.pop();
for(i=;i<;i++)
{
if(trie[u][i])
{
f[trie[u][i]]=trie[f[u]][i];
Q.push(trie[u][i]);
}
else trie[u][i]=trie[f[u]][i];//优化
}
}
return ;
}
上面代码中的else那个优化是怎么回事呢?
实际情况中,若不存在trie[u][i]的转移边,那么就需要一直让u=f[u]直到找到一个有i的转移边的点,设该点为点v。那我们就直接让trie[u][i]=trie[v][i](也就是trie[u][i]=trie[f[u]][i])。
也正是因为如此,存在trie[u][i]的转移边时并不需要考虑trie[f[u]][i]的转移边不存在的情况(就算不存在,以前肯定也处理好了),直接赋值就OK了。这样不仅代码好写,而且优化了时间。
(三)匹配
接着输入一段文本,对着处理好nxt数组的trie进行匹配就可以了
详见代码:
void get_ans(char *a)
{
int u=,len=strlen(a);
for(i=;i<len;i++)//枚举主串每一个字符
{
int c=a[i]-'a';
u=trie[u][c];
for(int k=u;k && book[k]!=-;k=f[k])//统计所有以字符c结尾的单词
ans+=book[k],book[k]=-;//book置为-1,避免重复统计
}
}
差不多就讲完了
学之前觉得AC自动机是个高大上的东西,但其实搞懂之后就很奇妙了
板子:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std; inline int read()
{
int f=,x=;
char ch=getchar();
while(ch<'' || ch>'') {if(ch=='-') f=-; ch=getchar();}
while(ch>='' && ch<='') {x=x*+ch-''; ch=getchar();}
return x*f;
} int n,cnt,ans;
int trie[][],book[],f[];
int i;
queue <int> Q; void build(char *a)
{
int u=,len=strlen(a);
for(i=;i<len;i++)
{
int c=a[i]-'a';
if(!trie[u][c])
trie[u][c]=++cnt;
u=trie[u][c];
}
book[u]++;
return ;
} void bfs()
{
for(i=;i<;i++)
if(trie[][i])
Q.push(trie[][i]);
while(!Q.empty())
{
int u=Q.front();
Q.pop();
for(i=;i<;i++)
{
if(trie[u][i])
{
f[trie[u][i]]=trie[f[u]][i];
Q.push(trie[u][i]);
}
else trie[u][i]=trie[f[u]][i];
}
}
return ;
} void get_ans(char *a)
{
int u=,len=strlen(a);
for(i=;i<len;i++)
{
int c=a[i]-'a';
u=trie[u][c];
for(int k=u;k && book[k]!=-;k=f[k])
ans+=book[k],book[k]=-;
}
} int main()
{
n=read();
char a[];
for(int k=;k<=n;k++)
{
scanf("%s",a);
build(a);
}
bfs();
scanf("%s",a);
get_ans(a);
printf("%d",ans);
return ;
}
AC代码(仅供参考)
本文部分内容参考《信息学奥赛一本通.提高篇》第二部分第四章 AC自动机
若需转载,请注明https://www.cnblogs.com/llllllpppppp/p/9839718.html
~NOIP2018 加油~
浅谈AC自动机的更多相关文章
- 浅谈AC自动机模板
什么是AC自动机? 百度百科 Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法. 要学会AC自动机,我们必须知道什么是Trie,也就是字典树.Tr ...
- 浅谈后缀自动机SAM
一下是蒟蒻的个人想法,并不很严谨,仅供参考,如有缺误,敬请提出 参考资料: 陈立杰原版课件 litble 某大神 某大神 其实课件讲得最详实了 有限状态自动机 我们要学后缀自动机,我们先来了解一下自动 ...
- 浅谈一类「AC自动机计数」问题
最近写了几道AC自动机的题.这几题主要考察的是对AC自动机的浅层理解套上计数. 几道计数题 [AC自动机]bzoj3172: [Tjoi2013]单词 把被动贡献看成主动贡献. [状态压缩dp]119 ...
- 浅谈算法——AC自动机
在学习AC自动机之前,你需要两个前置知识:Trie树,KMP 首先我们需要明白,AC自动机是干什么的(用来自动AC的) 大家都知道KMP算法是求单字符串对单字符串的匹配问题的,那么多字符在单字符上匹配 ...
- 从Trie谈到AC自动机
ZJOI的SAM让我深受打击,WJZ大神怒D陈老师之T3是SAM裸题orz...我还怎么混?暂且写篇`从Trie谈到AC自动机`骗骗经验. Trie Trie是一种好玩的数据结构.它的每个结点存的是字 ...
- AC自动机模板浅讲
Description 给你N个单词,然后给定一个字符串,问一共有多少单词在这个字符串中出现过(输入相同的字符串算不同的单词,同一个单词重复出现只计一次). Input 第一行一个整数N,表示给定单词 ...
- 小菜鸟 菜谈 KMP->字典树->AC自动机->trie 图 (改进与不改进)
本文的主要宗旨是总结自己看了大佬们对AC自动机和trie 图 的一些理解与看法.(前沿:本人水平有限,总结有误,希望大佬们可以指出) KMP分割线--------------------------- ...
- 【转】Android Canvas的save(),saveLayer()和restore()浅谈
Android Canvas的save(),saveLayer()和restore()浅谈 时间:2014-12-04 19:35:22 阅读:1445 评论:0 收藏: ...
- 浅谈OCR之Onenote 2010
原文:浅谈OCR之Onenote 2010 上一次我们讨论了Tesseract OCR引擎的用法,作为一款老牌的OCR引擎,目前已经开源,最新版本3.0中更是加入了中文OCR功能,再加上Google的 ...
随机推荐
- Atitit s2018.5 s5 doc list on com pc.docx Acc 112237553.docx Acc baidu netdisk.docx Acc csdn 18821766710 attilax main num.docx Atiitt put post 工具 开发工具dev tool test.docx Atiitt 腾讯图像分类相册管家.docx
Atitit s2018.5 s5 doc list on com pc.docx Acc 112237553.docx Acc baidu netdisk.docx Acc csdn 1882 ...
- 基于Python37配置图片文字识别
以管理员权限打开cmd控制台. 1.如何安装PIL 输入下面命令:pip install Pillow 参考:https://www.cnblogs.com/mrgavin/p/8177841.htm ...
- maven私服不能重复部署解决
1.报错 Return code is: 400, ReasonPhrase: Repository does not allow updating assets: maven-releases. 2 ...
- OLT、分光器、ONU直接的关系
- java.util.function 中的 Function、Predicate、Consumer
函数式接口: 函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但可以有多个非抽象方法的接口. 函数式接口可以被隐式转换为 Lambda 表达式. Function ...
- 【规范】前端编码规范——jquery 规范
使用单引号 不推荐 $("div").html("<img src='1.jpg'>"); 推荐 $('div').html('<img sr ...
- ROS工作空间和程序包创建
预备工作后面操作中我们将会用到ros-tutorials程序包,请先安装: $ sudo apt-get install ros-<distro>-ros-tutorials 将 < ...
- ubuntu下同时安装anaconda2与anaconda3,并分别安装与之对应的软件
1.安装anaconda2 参考网址:https://www.cnblogs.com/chamie/p/8876271.html 2.安装anaconda3 转载:https://blog.csdn. ...
- javascript:没有定义的变量和没有定义的属性
1. 没有定义的变量 window.onload = function() { alert(a); // 报错: Uncaught ReferenceError: a is not defined / ...
- 按位与(&)和按位或(|)
/** * 按位与 : & * 按位或 : | */ public class Demo { /** * 按位与: 为什么(5 & 9)的值等于1 * 按位或: 为什么(5 | 9)的 ...