自动ac机

system("poweroff");		// linux
system("shutdown -s -f"); // windows

ac自动机

在计算机科学中,Aho–Corasick算法是由Alfred V. Aho和Margaret J.Corasick 发明的字符串搜索算法,用于在输入的一串字符串中匹配有限组“字典”中的子串 。它与普通字符串匹配的不同点在于同时与所有字典串进行匹配。算法均摊情况下具有近似于线性的时间复杂度,约为字符串的长度加所有匹配的数量。然而由于需要找到所有匹配数,如果每个子串互相匹配(如字典为a,aa,aaa,aaaa,输入的字符串为aaaa),算法的时间复杂度会近似于匹配的二次函数。

反正每次都懒得写解释,直接复制百度百科

比如

kmp应该会吧(不会也没关系),kmp就是在一个字符串中匹配一个子串,那么ac自动机就是在一个字符串中匹配n个字串。

比如字符串为 abcde ,我们要在其中匹配 a bc cde ac 四个字符串,通过朴素方法+kmp优化,复杂度也会随n的增长而增长,但如果有了ac自动机,这一切都会迎刃而解。(挺符合ac观念的_

Trie树

在学习ac自动机前,先要学习Trie树,是什么东西呢?我们可以想一下,当我们需要储存字符串时,应该怎么储存呢?:

当然是开辟一个数组储存啦:

但如果我们要储存两个字符串,就变成了:

需要的空间翻了一倍,如果我们储存成千上万的字符串,直接 爆炸~~

如果我们使用这个trie树,以树的方式储存,就可以合并为(抱歉,下面都忘记画 d 了……):

瞬间,空间缩小了n倍。但是,我们很快就可以发现,如果我们储存的是 znpdcoznp 这两个字符串,就会成为:

我们怎样才能知道这两个字符串分别是 znpdcozn 还是 znpdcoznp 还是 znpdcoznpd 还是 znpdcoznpdc ……呢

所以我们需要定义一个终止符,用红色表示:

很好,现在就很明显知道我们储存的是 znpdcoznp 了。

Code

inline void insert(){
int p=0;
for(int i=1;s[i]!='\0';i++){
if(!trie[p][s[i]]){
trie[p][s[i]]=++cnt;
}
p=trie[p][s[i]];
}
tail[p]++;
}

ac自动机

开始进入正题,当我们使用ac自动机时,我们需要想到kmp的一个理念——不回溯,我们在kmp中使用nxt数组防止回溯,那么我们在ac自动机中用fail数组防止回溯。比如:

中,我们如果在匹配 abcd 时出错,我们还可以匹配 bcd ,如果还出错,就匹配 cd ……:

这就是全部的fail指针。

那么如果构建fail指针呢?

构建fail

我们举一个非常有代表性的例子:假设有5个模式串she he say shr her和一个文本串yasherhs,要求在文本串中查找有多少个模式串出现过。

我们先建树:

首先,第一点,我们都知道第一层的s,h 如果就错了,下面的就不用想了,直接回到root根:

接着,我们可以遍历s的每一个点h,a,首先是h,我们可以发现h在s的fail指针中有:

我们就可以直接把它的fail指过去:

为什么可以直接指过去

因为我们的父亲节点根据遍历顺序(个人感觉这个遍历顺序和 bfs 差不多,甚至可以直接理解为bfs)肯定已经指定好fail了。我们可以尝试在父节点中的最优fail中找一找我们的失配节点。

欸,那你这时肯定会说了,如果trie树长这样:

直接指过去就会指向:

一个不存在的虚拟节点。

第三种情况

上面介绍了两种情况,作为根节点的子节点fail直接指向root,作为已匹配好的父节点的子节点,有另一个操作方式,可当我们出现上述指向不存在的节点时,应该怎么办呢?

所以我们在遍历子节点时不可以直接遍历子节点了,而是要把所有节点从a到z遍历一遍,对于真实存在的子节点按情况二处理,对于不存在的节点我们可以把它指向 父节点的fail对应子节点

有点绕,举个例子:

我们在给 bc 处理时:

除了要处理c,还应当处理 a,b,d,e,f,g,... 。这里我们举d的例子:

把这个d指向父节点中的最优fail的失配节点。

但是,新的点也是一个虚拟节点呀!!

没关系,我们等到遍历到紫色点上:

可以进行一样的操作,将新点连接到另一个新点:

假如最终这个新点也是一个虚拟节点:

没有关系,因为默认赋值为0,所以最终还是会跳到根节点:

不过注意,这里的跳转指的不是fail节点跳转,因为trie树中这些节点本身就不存在,更不会调用它们的fail。所以我们修改的是它们父亲节点的儿子指针。

综上,我们就有了——

Code

inline void makeFail(){
queue<int> q;//典型bfs写法
for(int i='a';i<='z';i++) if(trie[0][i]) q.push(trie[0][i]);//情况一,根节点的子节点可以直接赋值
while(!q.empty()){
int p=q.front();
q.pop();
for(int i='a';i<='z';i++){
if(trie[p][i]){//情况二,在它的父亲fail中找目标节点
fail[trie[p][i]]=trie[fail[p]][i];
q.push(trie[p][i]);//入队
}
else{
trie[p][i]=trie[fail[p]][i];//情况三,以免虚拟节点的出现
}
}
}
}

query

查询步骤就很简单了,但是我们要注意,为了防止重复遍历加两次,就要定义vis。

剩下就很简单了:

int query(){
int p=0,ans=0; for(int i=1;s[i]!='\0';i++){
p=trie[p][s[i]];
for(int j=p;vis[j]==false;j=fail[j]){
ans+=tail[j];
vis[j]=true;
}
} return ans;
}

All Code

#include<cstdio>
#include<queue>
using namespace std;
int n;
char s[1000010];
int trie[1000010]['z'+1];
int tail[1000010];
int fail[1000010];
bool vis[1000010];
int cnt;
inline void insert() {
int p=0;
for(int i=1; s[i]!='\0'; i++) {
if(!trie[p][s[i]]) {
trie[p][s[i]]=++cnt;
}
p=trie[p][s[i]];
}
tail[p]++;
} void makeFail() {
queue<int> q;
for(int i='a'; i<='z'; i++) if(trie[0][i]) q.push(trie[0][i]);
while(!q.empty()) {
int p=q.front();
q.pop();
for(int i='a'; i<='z'; i++) {
if(trie[p][i]) {
fail[trie[p][i]]=trie[fail[p]][i];
q.push(trie[p][i]);
} else {
trie[p][i]=trie[fail[p]][i];
}
}
}
} int query() {
int p=0,ans=0; for(int i=1; s[i]!='\0'; i++) {
p=trie[p][s[i]];
for(int j=p; vis[j]==false; j=fail[j]) {
ans+=tail[j];
vis[j]=true;
}
} return ans;
} int main() {
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%s",s+1);
insert();
}
makeFail();
scanf("%s",s+1);
printf("%d",query());
}

例题

洛谷P3808 【模板】AC 自动机(简单版)

说明一下,我在这里写了一个通知小彩蛋,在电脑端可以开启通知权限试试……QWQ

代码同上

ac自动机|非自动ac机(当然也有) 笔记+图解的更多相关文章

  1. AC自动机板子题/AC自动机学习笔记!

    想知道484每个萌新oier在最初知道AC自动机的时候都会理解为自动AC稽什么的,,,反正我记得我当初刚知道这个东西的时候,我以为是什么神仙东西,,,(好趴虽然确实是个对菜菜灵巧比较难理解的神仙知识点 ...

  2. 洛谷 P3808 【模板】AC自动机(简单版) 题解

    原题链接 前置知识: 字典树.(会 \(\texttt{KMP}\) 就更好) 显然呢,本题用 字典树 和 \(\texttt{KMP}\) 无法解决问题. 所以我们发明了一个东西: \(\textt ...

  3. AC 自动机学习笔记

    虽然 NOIp 原地爆炸了,目前进入 AFO 状态,但感觉省选还是要冲一把,所以现在又来开始颓字符串辣 首先先复习一个很早很早就学过但忘记的算法--自动 AC AC自动机. AC 自动机能够在 \(\ ...

  4. 给宝宝的AC自动机启蒙指南(宝宝的第一本)

    AC自动机 根据已有经验,学完虚数会变虚,然后写出的代码就不是人能看的了 所以我们来学实树罢(喜) 以上为废话博客背景 有限状态自动机 首先我们来了解一下自动机是啥. 说的通俗一点,我们可以把自动机看 ...

  5. 【原创】AC自动机小结

    有了KMP和Trie的基础,就可以学习神奇的AC自动机了.AC自动机其实就是在Trie树上实现KMP,可以完成多模式串的匹配.           AC自动机 其实 就是创建了一个状态的转移图,思想很 ...

  6. POJ2778 DNA Sequence(AC自动机+矩阵快速幂)

    题目给m个病毒串,问不包含病毒串的长度n的DNA片段有几个. 感觉这题好神,看了好久的题解. 所有病毒串构造一个AC自动机,这个AC自动机可以看作一张有向图,图上的每个顶点就是Trie树上的结点,每个 ...

  7. POJ 2778 DNA Sequence (AC自动机,矩阵乘法)

    题意:给定n个不能出现的模式串,给定一个长度m,要求长度为m的合法串有多少种. 思路:用AC自动机,利用AC自动机上的节点做矩阵乘法. #include<iostream> #includ ...

  8. HDU 2222 Keywords Search(AC自动机)题解

    题意:给你几个keywords,再给你一段文章,问你keywords出现了几次. 思路:这里就要用到多模匹配算法AC自动机了,AC自动机需要KMP和字典树的知识,匹配时是在字典树上,失配我们就要用到类 ...

  9. 2017ACM暑期多校联合训练 - Team 8 1006 HDU 6138 Fleet of the Eternal Throne (字符串处理 AC自动机)

    题目链接 Problem Description The Eternal Fleet was built many centuries ago before the time of Valkorion ...

  10. AC自动机学习小结

    AC自动机 简要说明 \(AC\) 自动机,全称 \(Aho-Corasick\ automaton\) ,是一种有限状态自动机,应用于多模式串匹配.在 \(OI\) 中通常搭配 \(dp\) 食用. ...

随机推荐

  1. 信奥赛题1001:Hello,World!

    这个题实在是太简单的了,无法比喻,直接付代码! //c++ #include<bits/stdc++.h> using namespace std; int main() { cout&l ...

  2. Node: 使用nvm切换node版本

    软件下载 https://github.com/coreybutler/nvm-windows/releases/tag/1.1.7 解压并安装 双击程序一路安装即可.安装完成后,在控制台输入nvm出 ...

  3. 整理不错的opencv博客

    https://me.csdn.net/column/u013095718 更全的博客: https://blog.csdn.net/zhmxy555/column/info/opencv-tutor ...

  4. CSS:使用透明色

    使用如下代码: background-color="#00000000"

  5. ABC274 题解

    A 题目:给定 \(A,B\) 输出 \({B}\over{A}\) 保留 \(3\) 位小数. 简答题,和A+B problem 一样,除一除,保留一下小数. B 题目:给定一个 \(n\) 行 \ ...

  6. HTTPS 是这样握手的

    HTTP协议默认是明文传输,存在一定的安全隐患,容易被中间人窃听和攻击,在 加密解决HTTP协议带来的安全问题 中提到使用哈希.对称加密.非对称加密等方式对数据加密,能解决数据安全的问题. 以上加密方 ...

  7. 微信小程序上传文件操作示范

    社会实践心得体会格式要求 提交的心得体会应为word文档,且图文并茂,全文段前.段后0,1.5倍行距. 题目:自拟,方正小标宋简体,小二号,加粗,居中. 个人信息:题目下方,宋体,小四号,加粗,居中, ...

  8. 【LaTeX】制作 PPT(更新中)

    目录 Beamer 模板 特性 frame 与 slide \pause itemize 中的尖括号 <strat-end> 参考资料 Beamer 模板 PPT 推荐用 Beamer 模 ...

  9. 问题排查:nginx能跑,但是只能跑一会,不能跑多了

    背景 上周都是查测试环境的问题,比如,我上一篇写的问题排查:nginx的反向代理感觉失效了一样,就是说这个事的.在文章里,最终查到是nginx的全连接队列满了(每个监听端口有个队列,完成三次握手的请求 ...

  10. 《SQL与数据库基础》06. 函数

    目录 函数 字符串函数 数值函数 日期函数 流程函数 本文以 MySQL 为例 函数 函数是指一段可以直接被另一段程序调用的程序或代码. 要查看函数操作的结果,可以使用 SELECT 函数(参数); ...