AC自动机

根据已有经验,学完虚数会变虚,然后写出的代码就不是人能看的了

所以我们来学实树罢(喜)

以上为废话博客背景


有限状态自动机

首先我们来了解一下自动机是啥。

说的通俗一点,我们可以把自动机看成一张有向图,有一个点在起始节点。每当你输入一个合法的东西,这个点就会按照一定规则在边上移动。也就是说,你输入一些东西,这个点就会移动到一个确定的地方是不是很自动呢

我们接触到的第一个自动机其实不是AC自动机,而是trie树。大家可以看一看trie树是不是符合上面这些要求。

当然我是认为这个最大的用处在于让学习AC自动机的人明白trie树很重要。啥?trie树上跑KMP?AC自动机的数组可比KMP好懂多了。相信我。


引入

当然我们是可以按照自动机的结构来讲解的。但那样宝宝就看不懂了。

现在有这样一个问题:有很多模式串和一个文本串,要求有几个模式串在文本串中出现过。

显然,我们可以用模式串建出一棵trie树,一个字母一个字母的跑,跑不到了就重头再来。

但是这很费时间!如果我们能在树上找到失配字符串的后缀,那么失配以后,我们可以直接跳到树上的后缀继续匹配的!:

这是因为,尽管失配了,但我们仍能匹配她的后缀,无需再次重头开始。

AC自动机就是干这个的。


AC自动机的构造

AC自动机,实现的实际上就是多模式串的匹配。我们把这些模式串建成一棵trie树,这是引入里面提到的操作。

我认为,

接下来,我们设数组fail[u]为当在点u失配时可以跳到哪个点接着匹配。

我们肯定希望跳到的点所匹配的长度越大越好,所以我们规定fail[u]为u在trie树中的最长后缀所在节点。

我们来讨论一下怎么求fail。首先显然,与trie树虚拟根节点(这里我设为0)相连的边的fail肯定是0。然后我们根据一个点u的fail去推儿子ch[u][i]的fail,这样的好处是不用记录父亲。

为了表示方便,我们把ch[u][i]设为v

但首先,我们需要判断v存不存在。

  1. v存在,此时如果fail[u]也存在连向相同字符的边,那么我们就把fail[v]赋值为ch[fail[u]][i](最长后缀和字符串同步加一还是最长后缀)。如果不存在,那我们就需要找到fail[u]的fail,再进行一次判断,因为此时她是有可能存在的最长后缀了。如果还不存在,我们还有再来一次,直到存在或到0。。。好麻烦诶

别担心,我们之后会让这个步骤变得简单。现在我们来看第二种情况:v不存在。此时我们不需要更新v的fail节点,但我们可以想一个问题:当v不存在时,之后跳到u,想连到v,发现v不存在后,还得向上跳。那么,我们为什么不直接把v赋值成上面的某个有这条边的节点or0呢?

我们优化完的策略如下:如果v不存在,那么我们将ch[u][i]设为ch[fail[u]][i],如果存在,不动ch数组,将fail[v]赋值为ch[fail[u]][i]。

为什么这样能达到同样的效果呢?当v存在时,之后连向u的节点,都会把ch赋值为v,直到有另一个更大的后缀存在。当v不存在时,会向fail连,最终会连到上一个存在的节点,或者直接连到0。

大家可以画个图理解成一下,最终会产生这种状况:

ch[u][i] = ch[fail[u]][i] = ch[fail[fail[u]]][i] = ... = w, ch[fail[w]][i] = ch[fail[fail[w]]][i] = ... = 0

上面这种情况不一定准确啊。。。只是想让大家理解一下如果不存在,会直接跳到上一个存在这条边的节点。

或者,如果你的脑回路与我相近,可以尝试思考这个与并查集路径压缩之间的联系。。。

这样子我们就可以直接fail[v]=ch[fail[u]][i]了。因为就算fail[u]并没有这条边,ch[fail[u]][i]也被赋为了最近的一个有这条边的节点。如果都没有,就会干脆赋为0。

由于我们要一层一层的求fail数组,所以我们采用BFS

void build()
{
queue<int> q;
for(int i = 0;i < 26;i ++) if(ch[0][i]) q.push(ch[0][i]);
while(!q.empty())
{
int u = q.front();
q.pop();
for(int i = 0;i < 26;i ++)
{
if(!ch[u][i]) ch[u][i] = ch[fail[u]][i];
else fail[ch[u][i]] = ch[fail[u]][i],q.push(ch[u][i]);//只有存在才能进队
}
}
}

这样还有一个好处,就是我们查询时不需要fail数组了,直接按照trie树的方法查询就行。如果失配就会自动跳到最长的能配对的位置。


AC自动机的查询

这个依题目而定,我们来看几道题目吧

AC自动机模板

给定 \(n\) 个模式串 \(s_i\) 和一个文本串 \(t\),求有多少个不同的模式串在文本串里出现过。

两个模式串不同当且仅当他们编号不同。

对于 \(100\%\) 的数据,保证 \(1 \leq n \leq 10^6\),\(1 \leq |t| \leq 10^6\),\(1 \leq \sum\limits_{i = 1}^n |s_i| \leq 10^6\)。\(s_i, t\) 中仅包含小写字母。

这个是洛谷上面的模板题。

我们根据fail的定义发现,如果我们更新了一个点, 即使她的fail不会被走到,也会作为她的后缀存在于文本串中。也要进行更新。

于是我们搞个标记数组,按照trie树的匹配,每匹配到一个点就跳她的fail,一路跳到vis为1即可

注意这里只是统计出现过,所以我们可以标记到过的点,被标记的点就不再统计


int tiao(string s)
{
int n = s.size(), u = 0, ans = 0;
for(int i = 0;i < n;i ++)
{
int p = s[i] - 'a';
u = ch[u][p];
for(int j = u;j && !ton[j];j = fail[j]) ans += col[j], ton[j] = 1;
// 当一个点被标记过,他的所有的后缀肯定也被标记过,就不用继续跳了
}
return ans;
}

更nb的AC自动机模板

题目描述

有 \(N\) 个由小写字母组成的模式串以及一个文本串 \(T\)。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串 \(T\) 中出现的次数最多。

输入格式

输入含多组数据。保证输入数据不超过 \(50\) 组。

每组数据的第一行为一个正整数 \(N\),表示共有 \(N\) 个模式串,\(1 \leq N \leq 150\)。

接下去 \(N\) 行,每行一个长度小于等于 \(70\) 的模式串。下一行是一个长度小于等于 \(10^6\) 的文本串 \(T\)。保证不存在两个相同的模式串。

输入结束标志为 \(N=0\)。

这道题在洛谷上叫做AC自动机加强版,之后还会有个二次加强版

看起来是让我们求哪些串出现的最多,实际上就是求每个串出现的次数。

于是我们想,我们更新一个节点,她的所有后缀也会被更新。

然后我们把vis扔了,然后跳到一个串,就跳她的fail,一路更新。

看起来很不对,但实际上,每次fail最少也会使深度减少1,所以稍微算算就知道能过

最nb的AC自动机模板

就是上面那题加了数据范围

我们想,我们做第一个模板时,有vis,所以能过,但第二个模板时我们把vis扔了,所以就过不了了

但实际上我们可以从另一个角度优化,第二个模板每一次跳fail都会跳到很多重复的节点,每次+1+1太麻烦,我们攒到最后一起加就行了


AC自动机的应用

AC自动机优化DP

都是套路,如果不是套路,就是套路套套路。

设dp[i]为遍历到AC自动机上节点i时blabla的答案,有时也许要加个限制条件。然后外面套一层循环,里面从节点转移儿子就行

AC自动机的奇怪题目

[POI2000]病毒

题目描述

二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。

示例:

例如如果 \(\{011, 11, 00000\}\) 为病毒代码段,那么一个可能的无限长安全代码就是 \(010101 \ldots\)。如果 \(\{01, 11, 000000\}\) 为病毒代码段,那么就不存在一个无限长的安全代码。

现在给出所有的病毒代码段,判断是否存在无限长的安全代码。

输入格式

第一行包括一个整数 \(n\),表示病毒代码段的数目。

以下的 \(n\) 行每一行都包括一个非空的 \(01\) 字符串,代表一个病毒代码段。

输出格式

如果存在无限长的安全代码,输出 TAK,否则输出 NIE

之前的题目都是要匹配,这道题是不能匹配。而且只要她的fail不合法,她也就不合法

如果走到了走过的地方还没有到非法节点,那么就输出TAK

给宝宝的AC自动机启蒙指南(宝宝的第一本)的更多相关文章

  1. 浅析 AC 自动机

    目录 简述 AC 自动机是什么 AC 自动机有什么用 AC 自动机·初探 AC 自动机·原理分析 AC 自动机·代码实现 AC 自动机·更进一步 第一题 第二题 第三题 从 AC 自动机到 fail ...

  2. AC自动机讲解+[HDU2222]:Keywords Search(AC自动机)

    首先,有这样一道题: 给你一个单词W和一个文章T,问W在T中出现了几次(原题见POJ3461). OK,so easy~ HASH or KMP 轻松解决. 那么还有一道例题: 给定n个长度不超过50 ...

  3. 【POJ2778】DNA Sequence 【AC自动机,dp,矩阵快速幂】

    题意 题目给出m(m<=10)个仅仅由A,T,C,G组成的单词(单词长度不超过10),然后给出一个整数n(n<=2000000000),问你用这四个字母组成一个长度为n的长文本,有多少种组 ...

  4. LA_3942 LA_4670 从字典树到AC自动机

    首先看第一题,一道DP+字典树的题目,具体中文题意和题解见训练指南209页. 初看这题模型还很难想,看过蓝书提示之后发现,这实际上是一个标准DP题目:通过数组来储存后缀节点的出现次数.也就是用一颗字典 ...

  5. AC自动机详解 (P3808 模板)

    AC自动机笔记 0.0 前言 哇,好久之前就看了 KMP 和 Trie 树,但是似乎一直没看懂 AC自动机?? 今天灵光一闪,加上之前看到一些博客和视频,瞬间秒懂啊... 其实这个玩意还是蛮好理解的. ...

  6. AC自动机--summer-work之我连模板题都做不出

    这章对现在的我来说有点难,要是不写点东西,三天后怕是就一无所有了. 但写这个没有营养的blog的目的真的不是做题或提升,只是学习学习代码和理解一些概念. 现在对AC自动机的理解还十分浅薄,这里先贴上目 ...

  7. 使用AC自动机解决文章匹配多个候选词问题

    解决的问题 KMP算法用于单个字符串匹配,AC自动机用于文章中匹配多个候选词. 流程 第一步,先将候选词先建立前缀树. 第二步,以宽度优先遍历的方式把前缀树的每个节点设置fail指针, 头节点的fai ...

  8. 基于trie树做一个ac自动机

    基于trie树做一个ac自动机 #!/usr/bin/python # -*- coding: utf-8 -*- class Node: def __init__(self): self.value ...

  9. AC自动机-算法详解

    What's Aho-Corasick automaton? 一种多模式串匹配算法,该算法在1975年产生于贝尔实验室,是著名的多模式匹配算法之一. 简单的说,KMP用来在一篇文章中匹配一个模式串:但 ...

  10. python爬虫学习(11) —— 也写个AC自动机

    0. 写在前面 本文记录了一个AC自动机的诞生! 之前看过有人用C++写过AC自动机,也有用C#写的,还有一个用nodejs写的.. C# 逆袭--自制日刷千题的AC自动机攻克HDU OJ HDU 自 ...

随机推荐

  1. SQL server 清空数据库所有表

    use [数据库名]GOdeclare @sql varchar(8000)while (select count(*) from sysobjects where type='U')>0beg ...

  2. 微信小程序 的 openid,appid,unionid

    openid: 用户在同一个小程序下的唯一表示,即同一个用户在不同的小程序下的openid是不同的 appid 小程序唯一凭证,即 AppID,每个小程序都有自己的一个appid unionid 如果 ...

  3. Flutter之EdgeInsets

    EdgeInsets我们看看EdgeInsets提供的便捷方法: fromLTRB(double left, double top, double right, doublebottom):分别指定四 ...

  4. vue父子组件,子组件调用父组件方法

    问题描述:在table页面修改数据后,想刷新页面.修改页面以子组件的形式写的,现在想在子组件里面调用父组件的方法实现页面刷新! 将问题google后,以下两种方法都尝试过了,但是不起作用......大 ...

  5. IOS弹出系统键盘后,页面不恢复

    <script> var u = navigator.userAgent, app = navigator.appVersion var isIOS = !!u.match(/\(i[^; ...

  6. double 四舍五入和去尾

    // import java.math.RoundingMode;// import java.text.NumberFormat; double d= 1.345233; //四舍五入 保留两位小数 ...

  7. 【APT】海莲花组织DLL样本分析

    前言 样本来源Twitter,之前的文章已经完整分析过一个类似的DLL样本,这次做个简单记录. 样本分析 样本信息如下: DLL文件共有40个导出函数: 导出函数内容基本一致,恶意代码都在DllMai ...

  8. 更多Linux实用命令

    更多实用命令 进程相关 当程序运行在系统上时,我们称之为进程(process).想监测这些进程,需要熟悉 ps/top 等命令的用法.ps 命令好比工具中的瑞士军刀,它能输出运行在系统上的所有程序的许 ...

  9. SpringBoot系列---【maven项目引入第三方jar包并打包发布】

    一.问题 项目中经常会碰到这样的问题,我们做的项目依赖别人打好的jar包,这种我们可以有两种途径解决,第一种是上传到私服,再从我们的项目去引入pom坐标,这种适合有私服账号或者自己会搭建私服的,成本有 ...

  10. python中将各种类型表达式中的 \ 符号,都看作转义符

    python中将各种类型表达式中的 \ 符号,都看作转义符,如 \n 代表换行  \t  代表空tab,还发现 \a 相当于什么都不是,改行末尾的 \ 的意思是该行还没写完,接着下边一行的内容. 转义 ...