自动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. java反序列化与反序列化

    java反序列化漏洞 JAVA反序列化漏洞是由于开发者重写了readObject方法,该readObject方法方法调用了别的方法,最终执行到了例如Transfrom方法的危险方法 java序列化过程 ...

  2. iostream,iostream.h,stdio.h的纠葛

    include <iostream.h>非标准输入输出流 include 标准输入输出流 C++中为了避免名字定义冲突,特别引入了"名字空间的定义",即namespac ...

  3. typedef和define有什么区别

    typedef 和define 都是替一个对象取一个别名,以此增强程序的可读性,区别如下: 使用不用 define 定义后面不用加分号,并且它的别名在对象的前面 typedef需要加分号,并且它的别名 ...

  4. Unity的IPostprocessBuild:深入解析与实用案例

    Unity IPostprocessBuild技术文章 Unity IPostprocessBuild是Unity引擎中的一个非常有用的功能,它可以让开发者在构建项目后自动执行一些操作.这个功能可以帮 ...

  5. C# 多线程访问之 SemaphoreSlim(信号量)【进阶篇】

    SemaphoreSlim 是对可同时访问某一共享资源或资源池的线程数加以限制的 Semaphore 的轻量替代,也可在等待时间预计很短的情况下用于在单个进程内等待. 由于 SemaphoreSlim ...

  6. 解决使用sqlalchemy时MySQL密码含有特殊字符问题

    前言 当MySQL密码中含有特殊字符时,使用sqlalchemy大概率连不上,这时候就需要from urllib.parse import quote_plus as urlquote. 示例代码 f ...

  7. 4.4 C++ Boost 数据集序列化库

    Boost 库是一个由C/C++语言的开发者创建并更新维护的开源类库,其提供了许多功能强大的程序库和工具,用于开发高质量.可移植.高效的C应用程序.Boost库可以作为标准C库的后备,通常被称为准标准 ...

  8. AI绘画关键词Prompt:分享一些质量比较高的StableDiffusion(SD)关键词网站

    今天向大家推荐一些SD(StableDiffusion)高质量的 关键词 网站.这些网站的质量可靠,能为大家在创建 AI 绘画时提供有效的参考.以下是六个推荐的网站,优缺点分析. 有几个质量还算是挺高 ...

  9. 怎么选择API接口来获取自己想要的数据

    在今天的数字时代,数据变得越来越重要,API接口也成为了获取数据的一种重要方式.无论是开发自己的应用程序还是进行市场营销,数据的获取都是非常必要的.但是,如何选择API接口来获取自己想要的数据呢? 以 ...

  10. 完全可复制、经过验证的 Go 工具链

    原文在这里. 由 Russ Cox 发布于 2023年8月28日 开源软件的一个关键优势是任何人都可以阅读源代码并检查其功能.然而,大多数软件,甚至是开源软件,都以编译后的二进制形式下载,这种形式更难 ...