写在前面:从10月23日开始写这篇博文,离NOIP2018只有十多天了。坚持不停课的倔强蒟蒻(我)尽量每天挤时间多搞一搞信竞(然而还要准备期中考试)。NOIP争取考一个好成绩吧。

一、简介

AC自动机,全称Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法。(并不是让你的代码自动AC的算法)

AC自动机是必须建立在KMP和Trie的基础上的。所以如果你不会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自动机是个高大上的东西,但其实搞懂之后就很奇妙了

板子:

洛谷P3808

#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自动机的更多相关文章

  1. 浅谈AC自动机模板

    什么是AC自动机? 百度百科 Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法. 要学会AC自动机,我们必须知道什么是Trie,也就是字典树.Tr ...

  2. 浅谈后缀自动机SAM

    一下是蒟蒻的个人想法,并不很严谨,仅供参考,如有缺误,敬请提出 参考资料: 陈立杰原版课件 litble 某大神 某大神 其实课件讲得最详实了 有限状态自动机 我们要学后缀自动机,我们先来了解一下自动 ...

  3. 浅谈一类「AC自动机计数」问题

    最近写了几道AC自动机的题.这几题主要考察的是对AC自动机的浅层理解套上计数. 几道计数题 [AC自动机]bzoj3172: [Tjoi2013]单词 把被动贡献看成主动贡献. [状态压缩dp]119 ...

  4. 浅谈算法——AC自动机

    在学习AC自动机之前,你需要两个前置知识:Trie树,KMP 首先我们需要明白,AC自动机是干什么的(用来自动AC的) 大家都知道KMP算法是求单字符串对单字符串的匹配问题的,那么多字符在单字符上匹配 ...

  5. 从Trie谈到AC自动机

    ZJOI的SAM让我深受打击,WJZ大神怒D陈老师之T3是SAM裸题orz...我还怎么混?暂且写篇`从Trie谈到AC自动机`骗骗经验. Trie Trie是一种好玩的数据结构.它的每个结点存的是字 ...

  6. AC自动机模板浅讲

    Description 给你N个单词,然后给定一个字符串,问一共有多少单词在这个字符串中出现过(输入相同的字符串算不同的单词,同一个单词重复出现只计一次). Input 第一行一个整数N,表示给定单词 ...

  7. 小菜鸟 菜谈 KMP->字典树->AC自动机->trie 图 (改进与不改进)

    本文的主要宗旨是总结自己看了大佬们对AC自动机和trie 图 的一些理解与看法.(前沿:本人水平有限,总结有误,希望大佬们可以指出) KMP分割线--------------------------- ...

  8. 【转】Android Canvas的save(),saveLayer()和restore()浅谈

    Android Canvas的save(),saveLayer()和restore()浅谈 时间:2014-12-04 19:35:22      阅读:1445      评论:0      收藏: ...

  9. 浅谈OCR之Onenote 2010

    原文:浅谈OCR之Onenote 2010 上一次我们讨论了Tesseract OCR引擎的用法,作为一款老牌的OCR引擎,目前已经开源,最新版本3.0中更是加入了中文OCR功能,再加上Google的 ...

随机推荐

  1. RabbitMQ 学习专栏

    RabbitMQ 官网:http://www.rabbitmq.com/ 原创博文 1.揭开消息中间件RabbitMQ的神秘面纱 2. RabbitMQ 服务器之下载安装 3. RabbitMQ 之修 ...

  2. C++学习的书籍

    https://www.ossblog.org/master-c-programming-with-open-source-books/

  3. Git忽略规则.gitignore忽略node_modules文件夹

    在项目文件夹里添加.gitignore的文件 打开文件,在里面添加 /node_modules

  4. session_id()和session_regenerate_id()对原来session文件和其中数据是怎么处理的

    一.session_id()对原来session文件和里面的数据,是怎么处理的? 测验办法:<?php $sid = md5("aaad");session_id($sid) ...

  5. OE1、OE2、ON1、ON2路由有什么区别?

    OSPF的路由类型: 1 .O    域内路由 2 .O/A  域间路由 3 .OE1  域外路由,会累加METRIC值(默认20) 4 .OE2  域外路由,不累加METRIC值(默认20),由外部 ...

  6. 12 Best Live Chat Software for Small Business Compared (2019) 最佳的wordpress在线聊天工具推荐插件 来帮你和潜在客户互动

    12 Best Live Chat Software for Small Business Compared (2019)     Did you know that more than 67% of ...

  7. html button 点击 显示倒计时秒数

    如下: <html> <body> <input type="button" value="click" id="cli ...

  8. adsas数据库去O记

    adsas 数据库是用于广告买量数据分析;在17年由 Oracle 迁移到 PostgreSQL.现把之前的迁移笔记整理下.本次迁移表91个:存储过程21个:数据库大小2G. 1. 准备Postgre ...

  9. VS2017 编译Assimp

    1. 下载Assimp:http://assimp.sourceforge.net/ 2. 要下载和安装DirectX SDK 安装出现错误,错误代码s1023,解决方法:https://blog.c ...

  10. php冒泡排序详解笔记

    冒泡 /* * 冒泡排序(从小到大) * 介绍: * 它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来. * 思路: * 比较相邻的元素.如果第一个比第二个大,就交换他 ...