尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

AC自动机 学习笔记

AC自动机  学习笔记
📅 发布时间:2026/7/29 22:14:19

又一神秘字符串匹配算法。

前言

昨天晚上LZY发现自己的对拍有锅,急忙去修,最终成功将其倒退了一个版本。

介绍

AC 自动机是一种多模式串匹配算法,由 Alfred V. Aho 和 Margaret J.Corasick 发明,所以AC自动机的全名是:

\[\Large \mathcal{Aho–Corasick\space Automaton} \]

有A有C但没有AC

AC自动机的主要作用便是让多个模式串去匹配一个文本串
听起来有点像KMP?
确实用到了KMP的思想,此外也用到的Trie树,所以AC自动机是Trie树上的自动机。
所以不会KMP和Trie树的请掉头。
下面我们详细说明AC自动机的算法过程。

过程

回忆KMP

我们首先回忆一下你kmp是如何匹配的。
假设文本串为abacabababc,模式串为abab,假设预处理已完成,现在进行匹配。

首先我们很顺利的匹配了三个字符:

abacabababc
^^^!
abab

发现下一个字符匹配不上,KMP没有放弃所有已匹配的结果,既然已经匹配上了aba为什么不让a继续进行匹配呢?

abacabababc^!ab

发现从a开始匹配还是匹配不上,于是只能从头开始了。

abacabababc!a

匹配不上,跳过!

abacabababc^a
abacabababc^^^^abab

成功匹上一个模式串!但模式串后面没有了,所以我们让模式串的公共前后缀ab继续匹配。

abacabababc^^ab
abacabababc^^^^abab

又成功匹配上一个模式串!后面就没有了,匹配完成。

可以发现KMP的思想就是能不省就不省,最大化利用已经匹配好的串继续向下匹配。

AC自动机上的匹配

AC自动机上的匹配与KMP的匹配大同小异,唯一不同的地方就是失配后的决策。

  • KMP在失配后会将当前最长公共前后缀移上来继续尝试匹配。
  • AC自动机在失配后会尝试通过去除已经匹配的部分的一段前缀继续尝试匹配。

失配决策不一样的原因就在于AC自动机处理的是多模式串匹配,在其中一个没匹配上的时候,AC自动机会尝试匹配别的串。
这样说好像有点不太对,举个例子就明白了:
有五个模式串:her、she、sheighter、hter、thought,与一个文本串:shersheighthoughter。

shersheighthoughter
^^^
she
sheighter

首先she与sheighter都匹配上了she,she完成了匹配,但sheighter将在下一步失配。
于是考虑去除she的s前缀。

hersheighthoughter
^^
her

取出后sheighter与she由于没有开头的s无法继续匹配,但her可以继续尝试匹配。

hersheighthoughter
^^^
her

her匹配成功,由于无论尝试去除任何her的前缀对无法尝试新的匹配,所以直接去除整串整串也是前缀的一种。

sheighthoughter
^^^
she
sheighter

she再次完成匹配,由于sheighter还可以继续尝试匹配,所以我们先不去除前缀。

sheighthoughter
^^^^^^^!
sheighter

sheighter无法继续匹配,尝试去除前缀sheig。

hthoughter
^^
hter

去除后hter尝试匹配。

hthoughter
^^!
hter

匹配失败尝试去除前缀h。

thoughter
^^
thought

去除后thought,尝试匹配。

thoughter
^^^^^^^
thought

匹配成功,尝试去除前缀thoug继续匹配。

hter
^^
hter

去除后hter尝试匹配。

hter
^^^^
hter

匹配成功,算法结束。
以上就是AC自动机多模式串匹配的步骤,先别说你会不会写,但你肯定大概理解了。

Trie树优化匹配

上面那一堆找了一堆前缀,找前缀这活得找“专业人士”Trie树来干。
我们将上面一堆模式串扔进一颗Trie树里,就会得到这样的东西:
字典树
怎么画出来这么一个玩意
我习惯将字符标到节点上。
我们都知道,Trie树上的每一个点都表示一个字符串,那么我们完全可以整一个失配指针(\(fail\))指向每一个节点失配后去除前缀后的后缀部分。
就拿上面这一步举例:

sheighthoughter
^^^^^^^!
sheighter

sheighter无法继续匹配,尝试去除前缀sheig。
我们完全可以将sheight节点的 \(fail\) 指向ht。
失配
这样就可以快速找到下一个要匹配的串。

但说这么多,这个失配指针该这么指呢?
在Trie树中,每一个节点都有一堆指针,表示下一个字符,像sheigh这个节点,只有一个指向下一个节点sheigh的指针,为什么我们不尝试将sheigh指针在指向he与ht呢?
假设sheigh已经指向了he与ht,那么在找sheight的失配指针的时候,直接找它父亲所指向t的节点就找到了。
为什么可以让sheigh指向he与ht呢?
观察一下如果将sheigh的前缀扔掉,剩下h那么h这个字符串在往后接就可以接成he与ht也就是说sheigh的后缀与he、ht的前缀有公共部分,这样如果sheight这个t失配了,sheigh可以通过去除前缀到达ht这个串。
具体过程请看代码实现:

struct node{int son[26];                    //子节点指针int fail;                       //失配指针void init(){                    //初始化memset(son,0,sizeof(son));ans=fail=id=0;}
}T[NUM];
void insert(string s){              //插入一个节点,Trie树操作int u=0;for(char i:s){int &son=T[u].son[i-'a'];if(!son) son=++tot,T[son].init();u=son;}
}
void build(){                        //构建fail指针queue<int> q;                    for(int i=0;i<26;++i){if(T[0].son[i]) q.push(T[0].son[i]);  //先将跟的子节点入队}while(!q.empty()){int u=q.front();             //取出队首q.pop();for(int i=0;i<26;++i){if(T[u].son[i]){         //如果有这个子节点T[T[u].son[i]].fail=T[T[u].fail].son[i];  //子节点的fail就指向当前节点fail的对应节点。q.push(T[u].son[i]);}else{T[u].son[i]=T[T[u].fail].son[i];  //将子节点指向fail的对应子节点}}}
}

另附一张来自oi-wiki的图:

可以手摸一下理解其过程。

统计答案

先附上一张来自oi-wiki的图:

可以结合前面说过的AC自动机上的匹配理解其过程。
我们总不能在这些节点间跳来跳去把,这也太耗时间了。
我们可以发现,若只保留fail指针,那么剩余的图就是一颗树。
这是很显然的,所有节点的fail都指向比自身深度浅的节点,总共有节点数减一个加点,就是一棵树。
这样AC自动机上的问题就可以转移成子树求和的问题。

【模板】AC 自动机\(^{luoguP5357}\)

三倍经验题
子树求和问题可以使用拓扑排序或者树上dp(dfs),拓扑排序需要维护一下每个节点的度数。
下面使用拓扑排序过这道题

#include<bits/stdc++.h>
using namespace std;
const int NUM=2e5+10;
const int NUMM=2e6+10;int n;
namespace AC{struct node{int son[26];int ans;int fail;int deg;int id;void init(){memset(son,0,sizeof(son));ans=fail=id=0;}}T[NUM];int tot;int ans[NUM],pid;void init(){tot=pid=0;T[0].init();}void insert(string s,int &idx){int u=0;for(char i:s){int &son=T[u].son[i-'a'];if(!son) son=++tot,T[son].init();u=son;}if(!T[u].id) T[u].id=++pid;idx=T[u].id;}void build(){queue<int> q;for(int i=0;i<26;++i){if(T[0].son[i]) q.push(T[0].son[i]);}while(!q.empty()){int u=q.front();q.pop();for(int i=0;i<26;++i){if(T[u].son[i]){T[T[u].son[i]].fail=T[T[u].fail].son[i];T[T[T[u].fail].son[i]].deg++; //记录度数q.push(T[u].son[i]);}else{T[u].son[i]=T[T[u].fail].son[i];}}}}void query(string t){int u=0;for(char i:t){u=T[u].son[i-'a'];T[u].ans++;}}void topu(){                      //拓扑排序queue<int> q;for(int i=0;i<=tot;++i){if(T[i].deg==0) q.push(i);}while(!q.empty()){int u=q.front();q.pop();ans[T[u].id]=T[u].ans;int v=T[u].fail;T[v].ans+=T[u].ans;if(!--T[v].deg) q.push(v);} }
}
using namespace AC;string s;
int idx[NUM];int main(){init();cin>>n;for(int i=1;i<=n;++i){cin>>s;insert(s,idx[i]);ans[i]=0;}build();cin>>s;query(s);topu();for(int i=1;i<=n;++i){cout<<ans[idx[i]]<<'\n';}return 0;
}

不写DFS的原因:

  1. 我能力不足或是说我懒不想花时间去找根。
  2. 这篇其实是我贺的oi-wiki上的。

后记

AC自动机其实并不是很难,但出题人总是出一些奇奇怪怪的题。
反正就是不能让你好受。

相关新闻

  • 拯救消失的网页记忆:Wayback Machine浏览器扩展完全指南
  • 芯聆CLD6255(4 x 190W, 2 x 380W 或 2.1 模式 (2x190W + 1x380W ) @1% THD 数字输入 D 类音频播放器)
  • 软件测试工程师到底是做什么的?一文讲清职责、技能与成长路径

最新新闻

  • Antiestrogen ;CNVVPLpYDLLLE
  • 【路径规划】基于A星算法机器人静态避障路径规划matlab代码
  • JVM调优实战:从GC日志分析到参数优化,解决线上性能问题
  • 腾讯云发布:2026年最优惠购买入口在这里!AI工作室、中小企业、大型企业均可享受全网最低价 - 172号卡
  • virtme-ng完全指南:如何在几分钟内编译并测试Linux内核
  • Soar:革命性Linux包管理器横空出世!轻量级设计如何让静态二进制文件安装提速10倍?

日新闻

  • 金融舆情监测系统:多语言情感分析与实时可视化技术解析
  • QT C++调用Python异常处理:PyBind11实战与跨语言编程指南
  • A-47双麦回音消除模块:主次麦空间分布与差分连接对ENC性能的影响

周新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号