浅谈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的 ...
随机推荐
- springboot 项目中读取资源文件内容 如图片、文档文件
1 问题描述:在 springboot 项目中有时候会需要读取一些资源文件,例如 office的 docx 文档或者 png.jpg的图片.在多模块项目中资源文件需要放到启动项目的 Resources ...
- MySQL InnoDB 引擎的持久性与性能
MySQL 事务的 ACID 特性中,D 代表持久性(Durability):在使用 InnoDB 引擎时,当返回客户端一个成功完成事务的确认时, InnoDB 就会保证数据的一致性,即使该数据在此时 ...
- idea maven 集成多模块 module
首先第一步创建 顶级项目 也就是父项目 在创面那部中 不管你勾不勾 create from 那个选项 都无所谓,最终创建的项目要全删的 ,只保留pom.xml 父项目结构 接下来 创建子项目 也是 ...
- Spring-boot之 rabbitmq
今天学习了下spring-boot接入rabbitmq. windows下的安装:https://www.cnblogs.com/ericli-ericli/p/5902270.html 使用博客:h ...
- shell 十进制数字转十六进制字符串并将结果保存到变量
. . . . . 今天写测试脚本的时候需要将生成的十六进制值作为参数传递给某个命令,而循环生成的数值都是十进制的.在网上查了好久也没有找到如何将一个变量中的值进行进制转换,并保存到变量中,网上的办法 ...
- 【iCore4 双核心板_FPGA】实验十八:Niosii——基于内部RAM建立第一个软核
实验指导书及源代码下载地址: 链接:https://pan.baidu.com/s/1mjpwGJI 密码:6u8v iCore4链接:
- C语言 · 关联账户
标题:关联账户 为增大反腐力度,某地警方专门支队,对若干银行账户展开调查. 如果两个账户间发生过转账,则认为有关联.如果a,b间有关联, b,c间有关联,则认为a,c间也有关联. 对于调查范围内的n个 ...
- linux的挂载的问题,重启后就挂载就没有了
我用fdisk命令,分一个/dev/sda6出来,然后用mkfs格式化为ext3,然后挂载到根目录下的PPP文件夹中,挂载是成功了,但是用reboot和shutdown重启或关机后挂载就没有了 要修改 ...
- Dubbo 在maven项目中的应用
首先我们来看一下dubbo的架构: 所以通过此图,我们看到就是服务的提供者将服务注册到注册中心,服务的消费者从注册中心获取服务,monitor监控服务的调用. 关于dubbo的使用,我们举个简单的例子 ...
- Web重温系列(一):利用寄宿于IIS的WCF序列化文件
这两年一直在做WinForm,对于Web已经比较生疏了,其实之前做的也不是很多. 这两天做了一个小工具,功能很简单,就是想有个地方存放办公室同事的机器名和IP的信息,包括附加的用户名和更新时间.比较之 ...