AC自动机入门详解

C++ AC自动机算法 目录: AC自动机简介 如何构造 AC自动机 算法实现 1、建树 2、预处理失配指针 nex: 3、匹配 AC自动机例题推荐 AC自动机简介: 正所谓 AC自动机,就是能自动 AC 的算法。 AC自动机全称:Aho-Corasick automaton,该算法于1975年产生于贝尔实验室,是著名的多模匹配算法。 实际上,AC自动机就是在 Trie 树上实现KMP,来达到完成多模式串匹配的效... 阅读详情

一.AC自动机的引入.

我们都知道KMP可以用来一个子串与母串之间的匹配,只需要通过一个next指针就可以实现 O ( n + m ) O(n+m) O(n+m)匹配,已经达到了算法下界,是一个很优秀的算法了.

但是我们如何考虑多个子串与母串之间的匹配呢?

如果多个子串与母串之间的匹配用KMP来实现,效率可能就不那么高了,这可怎么办呢?这就是AC自动机的由来.

AC自动机是什么?其实就是在Trie树上跑KMP.


二.Trie树与KMP.

我们考虑单串时的KMP是怎么匹配的?利用的是next数组.

再考虑一下多串的一般套路?放进Trie树.

那么多串匹配怎么办?求Trie树上的next数组(在AC自动机里就叫fail指针了)!

考虑Trie树上在匹配一个串时,失配了怎么办?往fail指针跳!就像下面这张图:

我们认为这张图中所有相同颜色的链与线段都是相同的,那么当Trie树上红色的链失配时我们想要继续匹配,就必须要让红色线段的一个后缀(例如蓝色线段)与Trie树上的另外一条以根为一段的链相同(例如蓝色的链).fail指针也就是连接红色链的结尾与最长的满足条件的蓝色链的结尾(当然蓝色得比红色短)的一个指针(绿色箭头).

是不是突然感觉AC自动机很容易啊…然而理解fail指针并不代表会构造fail指针(fail指针学过KMP与Trie树就能懂了吧)…


三.构造fail指针.

构造fail指针其实很简单,因为我们发现fail指针一定是从深度大的节点指向深度小的节点,又可以发现一个节点的fail指针可以通过它父亲的fail指针构造,所以我们选择使用BFS构造fail指针.

具体就是BFS每一层,一个节点的fail指针就用它父亲的fail指针尝试是否能匹配,不能匹配就继续跳fail指针,知道能匹配或跳到了根(与KMP的next很像的跳法).

代码如下:

void Bfs_fail(){
  int co;
  tr[ord[co=1]=1].fail=1;
  for (int i=0;i<C;++i)
    if (tr[1].s[i]) tr[tr[1].s[i]].fail=1,q.push(tr[1].s[i]);
  while (!q.empty()){
  	int t=q.front();q.pop();
  	ord[++co]=t;
  	for (int i=0;i<C;++i)
	  if (tr[t].s[i]){
  	    int k=tr[t].fail;
  	    for (;k>1&&!tr[k].s[i];k=tr[k].fail);
  	    tr[tr[t].s[i]].fail=tr[k].s[i]?tr[k].s[i]:1;
  	    q.push(tr[t].s[i]);
  	  }
  }
}

值得注意的是,当我们去掉Trie树上fail指针外的边时,我们会发现Trie树上的节点与所有fail指针构成了一棵树!这棵树就被称为fail树.

既然我们有了一棵树,我们就可以对这棵树进行一些操作了.于是一些毒瘤题就这么应运而生(比如说BZOJ2434阿狸的打字机).


四.Trie图.

Trie图是AC自动机的确定化形式,也就是说Trie图是一个DFA(自动机分DFA或NFA).

Trie图的主要思想就是强行把一棵树变成图将 x x x为空的儿子指针 s [ i ] s[i] s[i]指向 x x x的fail指针的 s [ i ] s[i] s[i],相当于直接把fail树上的边加到了Trie树上.这样做会使代码变得简洁一些,而且它能很大程度上帮你少跳一些指针(查询的时候就不用写while循环了,Trie图只需要跳一次指针就够了).

代码如下:

void Bfs_fail(){
  int co;
  tr[ord[co=1]=1].fail=1;
  for (int i=0;i<C;++i)
    if (tr[1].s[i]) tr[tr[1].s[i]].fail=1,q.push(tr[1].s[i]);
    else tr[1].s[i]=1;
  while (!q.empty()){
  	int t=q.front();q.pop();
  	ord[++co]=t;
  	for (int i=0;i<C;++i)
	  if (tr[t].s[i]) tr[tr[t].s[i]].fail=tr[tr[t].fail].s[i],q.push(tr[t].s[i]);
	  else tr[t].s[i]=tr[tr[t].fail].s[i];
  }
}



五.运行.

接下来的运行以判定每一个串在母串中的出现次数为例.

查询最朴素的想法就是直接暴力在每一个点跳fail指针匹配,然而这样做是没有效率保证的.

考虑对于fail树,发现只要一个节点到根这一段可以匹配时,它在fail树上的父亲同样也会被匹配.所以我们只需要记录Trie树上每一个点被经过的次数,处理完之后让一个点的出现次数等于它的子树和.

普通AC自动机运行代码如下:

void Judge(char *s,int n){
  int k=1;
  for (int i=1;i<=n;++i){
  	for (;k>1&&!tr[k].s[s[i]-'a'];k=tr[k].fail);
  	if (tr[k].s[s[i]-'a']) k=tr[k].s[s[i]-'a'];
  	++cnt[k];
  }
  for (int i=cn;i>=1;--i){
  	int t=ord[i];
  	cnt[tr[t].fail]+=cnt[t];
  }
}

Trie图运行代码如下:

void Judge(char *s,int n){
  int k=1;
  for (int i=1;i<=n;++i) ++cnt[k=tr[k].s[s[i]-'a']];
  for (int i=cn;i>=1;--i){
  	int t=ord[i];
  	cnt[tr[t].fail]+=cnt[t];
  }
}



六.时间复杂度分析.

这里主要分析fail指针构造的时间复杂度.

我们考虑BFS时fail指针跳动的总次数,与KMP类似的,我们发现每跳一次fail指针都会使得深度减 1 1 1,而深度最多增加串长次.所以对于Trie树中插入的每一个串构造fail指针时间复杂度都是与串长同级的,设总共往Trie中插入了 n n n个字符,构造fail指针的总时间复杂度就是 O ( n ) O(n) O(n)的.

同样的,在查询串的时候外层枚举i的时间复杂度为 O ( m ) O(m) O(m),也就是说深度最多增长了 m m m次,所以fail指针的跳动次数最多也是 m m m次,总时间复杂度就是 O ( m ) O(m) O(m).但是由于后面我们还需要遍历一遍Trie树,所以一次查询总时间复杂度为 O ( n + m ) O(n+m) O(n+m).


七.例题与代码.

例题:luogu5357.

普通AC自动机代码如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=200000,M=2000000,C=26;

int n,id[N+9];
char s[M+9];
struct Trie{
  int s[C],fail;
}tr[N+9];
int cn;

void Build(){cn=1;}

int Insert(char *s,int n){
  int k=1;
  for (int i=1;i<=n;++i)
    if (tr[k].s[s[i]-'a']) k=tr[k].s[s[i]-'a'];
    else tr[k].s[s[i]-'a']=++cn,k=cn;
  return k;
}

int ord[N+9];
queue<int>q;

void Bfs_fail(){
  int co;
  tr[ord[co=1]=1].fail=1;
  for (int i=0;i<C;++i)
    if (tr[1].s[i]) tr[tr[1].s[i]].fail=1,q.push(tr[1].s[i]);
  while (!q.empty()){
  	int t=q.front();q.pop();
  	ord[++co]=t;
  	for (int i=0;i<C;++i)
	  if (tr[t].s[i]){
  	    int k=tr[t].fail;
  	    for (;k>1&&!tr[k].s[i];k=tr[k].fail);
  	    tr[tr[t].s[i]].fail=tr[k].s[i]?tr[k].s[i]:1;
  	    q.push(tr[t].s[i]);
  	  }
  }
}

int cnt[N+9];

void Judge(char *s,int n){
  int k=1;
  for (int i=1;i<=n;++i){
  	for (;k>1&&!tr[k].s[s[i]-'a'];k=tr[k].fail);
  	if (tr[k].s[s[i]-'a']) k=tr[k].s[s[i]-'a'];
  	++cnt[k];
  }
  for (int i=cn;i>=1;--i){
  	int t=ord[i];
  	cnt[tr[t].fail]+=cnt[t];
  }
}

Abigail into(){
  scanf("%d",&n);
  Build();
  for (int i=1;i<=n;++i){
  	scanf("%s",s+1);
  	id[i]=Insert(s,strlen(s+1));
  }
  scanf("%s",s+1);
}

Abigail work(){
  Bfs_fail();
  Judge(s,strlen(s+1));
}

Abigail outo(){
  for (int i=1;i<=n;++i)
    printf("%d\n",cnt[id[i]]);
}

int main(){
  into();
  work();
  outo();
  return 0;
}

Trie图:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=200000,M=2000000,C=26;

int n,id[N+9];
char s[M+9];
struct Trie{
  int s[C],fail;
}tr[N+9];
int cn;

void Build(){cn=1;}

int Insert(char *s,int n){
  int k=1;
  for (int i=1;i<=n;++i)
    if (tr[k].s[s[i]-'a']) k=tr[k].s[s[i]-'a'];
    else tr[k].s[s[i]-'a']=++cn,k=cn;
  return k;
}

int ord[N+9];
queue<int>q;

void Bfs_fail(){
  int co;
  tr[ord[co=1]=1].fail=1;
  for (int i=0;i<C;++i)
    if (tr[1].s[i]) tr[tr[1].s[i]].fail=1,q.push(tr[1].s[i]);
    else tr[1].s[i]=1;
  while (!q.empty()){
  	int t=q.front();q.pop();
  	ord[++co]=t;
  	for (int i=0;i<C;++i)
	  if (tr[t].s[i]) tr[tr[t].s[i]].fail=tr[tr[t].fail].s[i],q.push(tr[t].s[i]);
	  else tr[t].s[i]=tr[tr[t].fail].s[i];
  }
}

int cnt[N+9];

void Judge(char *s,int n){
  int k=1;
  for (int i=1;i<=n;++i) ++cnt[k=tr[k].s[s[i]-'a']];
  for (int i=cn;i>=1;--i){
  	int t=ord[i];
  	cnt[tr[t].fail]+=cnt[t];
  }
}

Abigail into(){
  scanf("%d",&n);
  Build();
  for (int i=1;i<=n;++i){
  	scanf("%s",s+1);
  	id[i]=Insert(s,strlen(s+1));
  }
  scanf("%s",s+1);
}

Abigail work(){
  Bfs_fail();
  Judge(s,strlen(s+1));
}

Abigail outo(){
  for (int i=1;i<=n;++i)
    printf("%d\n",cnt[id[i]]);
}

int main(){
  into();
  work();
  outo();
  return 0;
}
C++学习第九篇——AC自动机 AC自动机,听名字就让人心情愉悦,这个算法能让题目直接自己AC啊!!! 言归正传,在上一篇字典树的学习中,我们见识到了树形结构的优势,那么AC自动机就是在字典树上利用KMP的思想,KMP是字符串匹配算法。主要思想是当前匹配点失败后,立刻转移到下一个需要匹配的地方,在这之间省略一些没有必要的检索,有利于降低时间复杂度。 字典树的模板参照我之前一篇博客C++学习第八篇——字典树,本专栏c++固定算法为... 阅读详情

相关推荐

C++实现AC自动机

AC自动机,C++实现 #include <iostream> #include <vector> #include <queue> #include <cstring> using namespace std; struct Ac_Node { Ac_Node() { memset(child, NULL, sizeof child); } Ac_Node *child[26]; //26个子节点 Ac_

ljw695950695的博客 1024

AC自动机代码

AC自动机一点也不自动

m0_50966319的博客 561

AC自动机初步入门详解

前言 AC自动机(Aho-Corasick automaton)是KMP的升级版。KMP是单模匹配算法,处理在一个文本串中查找一个模式串的问题;AC自动机是多模匹配算法,能在一个文本串中同时查找多个不同的模式串。 多模块匹配问题:给定一个长度为n的文本S,以及k个平均长度为m的模式串P1,P2,…,Pn,要求搜索这些模式串出现的位置。 其实用KMP也能解决多模匹配问题,缺点是复杂度较高,需要对每个...

weixin_43888067的博客 746

波动数列,简易AC代码,详细讲解。

波动数列 这道题真的是需要自己花时间来理解细节。 思路: 如下图所示,该序列的首项是x,公差为set,set={a,-b}; 通过下边的第一幅图我们可以知道,set的数量为n*(n-1)/2 (公式:首项加末项除以2) 我们令cnt=n*(n-1)/2 (也就是set的数量) 通过这些分析我们可以知道满足这么一个条件:序列总和=Sn=nx+cntset 我们假设a的数量为num1,因为a,b数量的总和是cnt,则b的数量就是cnt-num1; 可推出一个表达式nx+a * num1-b(cnt-nu

QNBZ的博客 1211

【创作赢红包】Gurobi学术版证书更新与Python环境配置全攻略

本文详细解析了Gurobi学术版证书过期后的完整更新流程与Python环境配置方法。从彻底清理旧版残留、手把手注册获取新许可证,到软件安装、证书激活,最后重点讲解如何将gurobipy接口正确配置到Python的site-packages目录,并提供了常见问题排查方案,帮助用户高效解决证书与配置难题。

efc1234567的博客 613

深入解析以太网接口中的BOB Smith电路设计

本文深入解析以太网接口中的BOB Smith电路设计,详细介绍了其在共模噪声抑制、浪涌防护和EMI优化中的关键作用。通过实际案例和设计要点,帮助工程师正确选择75Ω电阻和高压电容等元件,优化PCB布局,解决电压型与电流型PHY的设计差异,以及千兆与百兆以太网的特殊考量。

weixin_28339967的博客 687

AC自动机入门+例题详解

想当年AC自动机算是我ACM路上的一道坎,长长的代码,不知所云的 fail 指针,做题目只有看题解才能过。今天,把AC自动机,重新复习了一遍,发现好多问题都迎刃而解了,这篇博客就总结一下我对AC自动机的认识。 前置知识 Trie字典树(需要借助Trie来存字符串) KMP(因为fail指针与next数组类似) 简介 AC自动机也是用来处理字符串匹配的问题的。它与KMP不同在,KMP是处理单模式串匹配问题,而AC自动机是用来处理多模式串的问题。例如,给出 n 个单词,再给出一个字符串 S,问有多少个单词.

hhhcbw的博客,欢迎各位来访 1248

Vue3+Vite+Electron桌面应用开发:从零到打包的完整避坑指南

本文是一份详尽的Vue3+Vite+Electron桌面应用开发实战指南。文章从项目初始化、环境配置、主进程构建、Vite深度优化,到最终的打包配置与优化,系统性地讲解了使用现代前端技术栈开发跨平台桌面应用的完整流程,并重点分享了开发与打包过程中的常见“坑点”与解决方案,旨在帮助开发者高效构建稳定可交付的桌面端应用。

hh234的博客 107

AC自动机算法详解(入门)

AC自动机算法详解首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。要搞懂AC自动机,先得有模式树(字典树)Trie和KMP模式匹配算法的基础知识。AC自动机算法分为3步:构造一棵Trie树,构造失败指针和模式匹配过

南风古 498

数字IC设计随笔之二(VCS、DVE|Verdi单步调试)

VCS+Verdi单步调试

ic疾风传 1万+

数字信号处理(一):Xilinx Vivado DDS IP核设计实例(文末附源码)

前言 在数字信号处理时我们经常会用到数字变频,包数字下变频(DDC)和数字上变频(DUC),这其中会用到Xilinx公司的DDS IP核或者Altera公司的NCO IP核来产生本振频率,以现数字域信号频谱搬移。本文我们通过例化Xilinx公司的DDS IP核来产生混频器本振输入频率,并给出Modelsim仿真测试结果。 1、Xilinx DDS IP简介 Xilinx公司的DDS IP核使用AX...

FPGA_Designer 1万+

hdu 2222 AC自动机入门详解

题意: 给n(1e4)个单词,每个单词长度小于等于50。 然后给一篇文章,长度最长为(1e6)。 求每个单词在文章中出现的总次数。 解析: 多校做到了ac自动机的题,来学习一下。 这题有个坑点就是单词会重复。 比如 /* 1 3 she she she shesheshe */ 这组数据,输出应该是3。 这涉及到模板的修改问题。 以下见代码中那句

渐近自由 661

SAP 与 Palantir 合作全景解析:这不是普通集成,而是把 ERP、数据语义层、云迁移与企业级 AI 串成一条价值链

SAP与Palantir的战略合作聚焦企业数字化转型核心痛点,通过深度技术整合构建"数据-决策-执行"闭环。合作包含四大主线:云迁移连续性保障、SAP业务数据云与Palantir平台对接、联合工程开发、高监管行业优先落地。技术实现上采用SAP认证插件,支持双向数据交互和实时业务动作触发。该合作突破了传统ERP系统局限,将业务语义与AI执行相结合,特别适合制造业供应链优化、财务争议处理等高价值场景,为企业在云迁移过程中实现业务连续性与智能化升级提供新范式。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 192

【AC自动机详解+入门模板】HDU 2222

第一次写AC自动机,发现也不是太难理解,可能自己以前看过KMP&Trie而且理解的比较透彻吧,看自动机没有太大的困难,因为它也是利用了KMP的思想,理解了KMP,再看自动机就不难了:) 转一下这个blog的描述先,感觉看起来比较好理解,可是他的代码就不敢恭维。。。http://www.cppblog.com/mythit/archive/2011/10/21/80633.html(有改动) 补

leolin_的专栏 1011

C# 接口和抽象类

文章目录1. 什么是接口和抽象类2. 接口与单元测试3. 依赖&耦合4. 依赖翻转5. 单元测试 1. 什么是接口和抽象类 接口和抽象类都是“软件工程产物” 具体类->抽象类->接口:越来越抽象,内部实现的东西越来越少 抽象类是未完全实现逻辑的类(可以有字段和非public成员,它们代表了“具体逻辑”) 抽象类为复用而生:专门作为基类来使用,也具有解耦功能 封装确定的,开放不确定的,推迟到合适的子类中去实现 接口是完全未实现逻辑的“类”(“纯虚类”;只有函数成员;成员全部publi

汽车电子软件领域知识分享 1013

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

AC自动机入门详解+例题 hdu2222

首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。要搞懂AC自动机,先得有字典树Trie和KMP模式匹配算法的基础知识。KMP算法是单模式串的字符匹配算法,AC自动机是多模式串的字符匹配算法。因为AC自动机算法是建立

Merry_hj的博客 606

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

AC自动机算法

AC自动机简介:  首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。要搞懂AC自动机,先得有字典树Trie和KMP模式匹配算法的基础知识。KMP算法是单模式串的字符匹配算法,AC自动机是多模式串的字符匹配算

Emilio563的博客 324

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

AC自动机简介

转载自: AC自动机简介:  首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。要搞懂AC自动机,先得有字典树Trie和KMP模式匹配算法的基础知识。KMP算法是单模式串的字符匹配算法,AC自动机是多模

Sped GO! 613

HDU 2222 AC自动机模版题

点击打开链接 题意:问前面给的所有串在最后一个串出现的次数 思路:这道题是AC自动机入门必做的题,所以没什么好说的,是个模版题,推荐一个大神写的算法详解,不懂得可以看一看,反正我是看他的稍稍懂了点#include #include #include #include #include using namespace std; typedef long long ll; const

Dan__ge的博客 924

hdu2222 Keywords Search & AC自动机学习小结

传送门:http://http://acm.hdu.edu.cn/showproblem.php?pid=2222 思路:AC自动机入门题,直接上AC自动机即可。 对于构建AC自动机,我们要做的只有三件事: 1)构建字典树 2)构建失败指针 3)构建trie图(这道题好像不做这一步也能A。。。但是这一步不做是会被卡成O(n^2)的。。。) 1)第一步还是比较好理解的

thy的专栏 711

AC自动机资料汇总

概要介绍,一篇不错的文章 AC自动机 关键字:AC自动机 自动机 有限状态自动机 Trie 字母树 字符串匹配 多串匹配算法 Note:阅读本文需要有KMP算法基础,如果你不知道什么是KMP,请看这里: http://www.matrix67.com/blog/article.asp?id=146   (Matrix67大牛写的) AC自动机是

Sky-J的代码篮子 756
上一篇: 斜率优化DP入门(BZOJ1010 玩具装箱题解)
下一篇: 竞赛图(有向完全图)上哈密顿路径的若干性质(BZOJ4727 [POI2017]Turysta 题解)
hezlik
博客等级 码龄9年 111粉丝 241原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值