自动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. 一文了解 io.LimitedReader类型

    1. 引言 io.LimitedReader 提供了一个有限的读取功能,能够手动设置最多从数据源最多读取的字节数.本文我们将从 io.LimitedReader 的基本定义出发,讲述其基本使用和实现原 ...

  2. 【2020GET】即构科技蒋宁波:教育行业客户需求的核心是什么?

    11月24日,由即构科技主办的2020GET大会教育科技分论坛在北京成功召开,来自叮咚课堂.小冰.360OS.蕃茄田艺术.即构科技的6位资深教育/科技大咖,在论坛上进行深度分享. 以下为即构科技联合创 ...

  3. Redis的设计与实现(1)-SDS简单动态字符串

    现在在高铁上, 赶着春节回家过年, 无座站票, 电脑只能放行李架上, 面对着行李架撸键盘--看过<Redis的设计与实现>这本书, 突然想起, 便整理下SDS的内容, 相对后面的章节, 算 ...

  4. EntityCleanFramework

    EF几乎是按照领域的概念诞生,它可以和Clean结合(ECF是我新想出的名字).ECF 是为了统一业务架构开发方式,也可以说成在 微服务架构 中服务的通用开发方式.当有了统一开发方式后,协作将更上一层 ...

  5. node:windows script host 錯誤 console未定义

    错误背景 在开发npm包时,碰到此项报错 解决方案 选中任意js文件,选择打开方式,指定到node中即可

  6. ChatGPT 助力开发人员改进代码的5个方式

    近年来,在软件开发中使用人工智能和机器学习变得越来越普遍.因此,开发人员开始转向像 OpenAI 的 ChatGPT 这样的工具来简化他们的工作,提高他们的工作效率.ChatGPT是一个由 OpenA ...

  7. 去中心化组件共享方案 —— Webpack Module Federation(模块联邦)

    在大型应用中, 我们可能会对其进行拆分,分成容器.主应用和多个子应用,使拆分后的应用独立开发与部署,更加容易维护.但无论是微应用.公共模块应用,都需要放到容器中才能使用. 如果多个应用之间希望资源共享 ...

  8. [gin]数据解析和绑定

    前言 go version: 1.18 本文主要包含JSON.Form.Uri.XML的数据解析与绑定. JSON数据解析与绑定 go代码 package main import ( "ne ...

  9. MIT6.s081/6.828 lectrue4:page tables 以及 Lab3 心得

    不管是计算机组成还是操作系统,虚拟内存都是其中的重要内容,所以这一节我会结合 CSAPP 第九章:虚拟内存 来一起复习(顺便一说,CSAPP 这一节的 lab 是要求设计一个内存分配器,也是很有意思的 ...

  10. 优化 Redis 集群缓存分配:解决节点间分配不均导致内存溢出问题

    一.Redis 集群部署简介 在现代应用程序中,缓存被广泛应用以提高性能和减轻后端数据库的压力.本文将探讨面对 Redis 集群缓存分配不均问题时的解决方法. 我们的 Redis 集群部署包括 3 主 ...