自动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. Python单元测试之道:从入门到精通的全面指南

    在这篇文章中,我们会深入探讨Python单元测试的各个方面,包括它的基本概念.基础知识.实践方法.高级话题,如何在实际项目中进行单元测试,单元测试的最佳实践,以及一些有用的工具和资源 一.单元测试重要 ...

  2. C#中using的三种用法

    1. 对命名空间的引用 引入命名空间,在使用类时可以省略复杂的前缀,类似于Java的import. using System.Text; 顺便提一句,C#10新特性支持全局using,如果关键字 gl ...

  3. CSDN这么公然爬取(piao qie)cnblogs的文章,给钱了吗?

    在CSDN网站经常看到有博客转载cnblogs的文章,开始还以为是网友自行转载,后来才发现,这些所谓的转载应该都是机器爬取(piao qie)过去的.不知道cnblogs对此怎么看. 下面看看几个示例 ...

  4. Cilium系列-9-主机路由切换为基于 BPF 的模式

    系列文章 Cilium 系列文章 前言 将 Kubernetes 的 CNI 从其他组件切换为 Cilium, 已经可以有效地提升网络的性能. 但是通过对 Cilium 不同模式的切换/功能的启用, ...

  5. python教程 入门学习笔记 第2天 第一个python程序 代码规范 用默认的IDLE (Python GUI)编辑器编写

    四.第一个python程序 1.用默认的IDLE (Python GUI)编辑器编写 2.在新建文件中写代码,在初始窗口中编译运行 3.写完后保存为以.py扩展名的文件 4.按F5键执行,在初始窗口观 ...

  6. Java代码审计之某博客

    对某博客的代码审计 在gitee上面找了一个个人博客项目,来进行实战代码审计,主要还是学习为主 技术菜菜,哪里错误希望师傅们指正 1.SQL注入 先了解Java Web中的数据传输流程 graph T ...

  7. Pandas 使用教程 JSON

    目录 JSON 转换为 CSV 简单 JSON 从 URL 中读取 JSON 数据: 字典转化为 DataFrame 数据 内嵌的 JSON 数据 复杂 JSON Pandas 可以很方便的处理 JS ...

  8. Python 基础面试第三弹

    1. 获取当前目录下所有文件名 import os def get_all_files(directory): file_list = [] # os.walk返回一个生成器,每次迭代时返回当前目录路 ...

  9. SpringBoot拦截器和动态代理有什么区别?

    在 Spring Boot 中,拦截器和动态代理都是用来实现功能增强的,所以在很多时候,有人会认为拦截器的底层是通过动态代理实现的,所以本文就来盘点一下他们两的区别,以及拦截器的底层实现. 1.拦截器 ...

  10. WPF开发必备

    类库 1.XamlFlair The goal of the XamlFlair library is to ease the implementation of common animations ...