AC自动机详解(附加可持久化AC自动机)
AC自动机
AC自动机,说白了就是在trie树上跑kmp(其实个人感觉比kmp容易理解)。是一种多匹配串,单个主串的匹配。概括来说,就是将多个匹配串构造一个trie树,对于每个trie树的节点构造nxt指针,最后把主串放在上面跑。
构造trie树
和普通的trie树构建一样,没有什么区别
inline void insert(char *s){
int l=strlen(s);
int u=;
REP(i,,l-){
int c=calc(s[i]);
if(!tree[u][c]) tree[u][c]=++total;
u=tree[u][c];
}
isend[u]++;//注意isend的具体处理根据题目而定
return ;
}
构造nxt数组
其实这一部分是AC自动机的核心,我们这样构造:对于每个节点,它的nxt是,它父亲的nxt的和它名字相同的儿子。如图,u的父亲是v,它父亲的nxt的a这个儿子就是u的nxt。

还有一种情况,就是如果节点u,它的没有a这个儿子,那么它就要把nxt[u]的a这个儿子当成他的儿子。

如图,因为u没有a的子节点,所以就连到nxt[u]的a子节点。
那么这么做的原因是什么?我们来看一下这个图:

如图,这个trie树中前7个节点的next都已经构造完成了(箭头表示他们的nxt,1的nxt是0,没有画出来).现在要找8的next。按照“它的nxt是,它父亲的nxt的和它名字相同的儿子”的原则,我们找到8的父亲,7,发现7的nxt,5也没有B这个儿子,这时候我们需要找5的next,2,最终发现2有B儿子,是4,将8连到4。
但是注意,其实我们这一个一个找nxt是可以省略的。如果按着刚才“因为u没有a的子节点,所以就连到nxt[u]的a子节点。”树就会变成这样(黑线表示连边,红线表示next)

5因为没有B儿子,就把他的nxt:2,的B儿子:4,当成自己的儿子,7也同理,因为它没有A儿子,所以把他的nxt的A儿子:2,当成自己的A儿子。再来看8,发现它的父亲的nxt,5,的B儿子是4,所以自己的next就是4了。这样减少了刚才一个一个找nxt的步骤。
inline void getnxt(){//整个代码用BFS实现
while(!Q.empty()) Q.pop();
REP(i,,) tree[][i]=;//一个非常重要的细节处理,我们加一个虚拟节点0,并将它的所有边都连到1,方便以后的运算
nxt[]=;
Q.push();
while(!Q.empty()){
int u=Q.front();//u是当前点,这时候nxt[u]已经处理过了,要处理的是u的儿子的nxt,也就是nxt[tree[u][i]]
Q.pop();
REP(i,,){//枚举u节点的每一个子节点
if(!tree[u][i]) tree[u][i]=tree[nxt[u]][i];//这就是刚才说的很重要的一步优化, 如果自己没有这个子节点,就把自己next的这个子节点当做自己的子节点。
else{
nxt[tree[u][i]]=tree[nxt[u]][i];//自己儿子的nxt等于自己nxt的儿子,这句话和“自己的nxt是,自己父亲的nxt的和它名字相同的儿子”的意思相同,只是主语从待更新节点变成已就更新节点。
Q.push(tree[u][i]);
}
}
}
return ;
}
查找
查找的具体实现是根据题目而定,我就拿这道题举个例子:给一大堆匹配串和一个主串,求有多少个匹配串在主串上出现过。
这种题的做法就是现在构建trie树的时候,把每个单词的结尾都记录一下:isend[i]++。最后跑一遍AC自动机,到每一个节点是ans+=isend[i];isend=0;这样听起来很简单,那么怎么遍历AC自动机呢?
循环遍历主串s,令u表示当前点,每当主串s到下一位时,u=tree[u][s[i]-‘a’](就是等于它的儿子)。然后对于每个u,循环它的nxt直到根。每到一个点就ans+=isend。具体看代码:
inline void search(){
int ans=;
int u=;
int l=strlen(t);
REP(i,,l-){//循环遍历主串
int c=calc(t[i]);//计算这个字符的ACCII码
int k=tree[u][c];
while(k>){//对于每一个u遍历它的nxt,直到根
if(isend[k]){
ans+=isend[k];//加上isend,记录答案
isend[k]=;
}
k=nxt[k];
}
u=tree[u][c];//遍历到它的儿子。
}
printf("%d\n",ans);
}
总结
再来回顾一下AC自动机的步骤:构建trie树,构建next数组,查找。其中next有两个原则:1、当这个节点没有字符c这个儿子时,把自己的next的c这个儿子当做自己的儿子
2、自己儿子的nxt等于自己nxt的儿子
附上代码:#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <stack>
#include <vector>
using namespace std;
#define MAXN 100010
#define INF 10000009
#define MOD 10000007
#define LL long long
#define in(a) a=read()
#define REP(i,k,n) for(int i=k;i<=n;i++)
#define DREP(i,k,n) for(int i=k;i>=n;i--)
#define cl(a) memset(a,0,sizeof(a))
inline int read(){
int x=,f=;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-;
for(;isdigit(ch);ch=getchar()) x=x*+ch-'';
return x*f;
}
inline void out(int x){
if(x<) putchar('-'),x=-x;
if(x>) out(x/);
putchar(x%+'');
}
int T,n;
int total=;
int nxt[],tree[][];
char in[];
int isend[];
char t[];
queue <int> Q;
int calc(char c){
return c-'a';
}
inline void insert(char *s){
int l=strlen(s);
int u=;
REP(i,,l-){
int c=calc(s[i]);
if(!tree[u][c]) tree[u][c]=++total;
u=tree[u][c];
}
isend[u]++;
return ;
}
inline void getnxt(){//整个代码用BFS实现
while(!Q.empty()) Q.pop();
REP(i,,) tree[][i]=;//一个非常重要的细节处理,我们加一个虚拟节点0,并将它的所有边都连到1,方便以后的运算
nxt[]=;
Q.push();
while(!Q.empty()){
int u=Q.front();//u是当前点,这时候nxt[u]已经处理过了,要处理的是u的儿子的nxt,也就是nxt[tree[u][i]]
Q.pop();
REP(i,,){//枚举u节点的每一个子节点
if(!tree[u][i]) tree[u][i]=tree[nxt[u]][i];//这就是刚才说的很重要的一步优化, 如果自己没有这个子节点,就把自己next的这个子节点当做自己的子节点。
else{
nxt[tree[u][i]]=tree[nxt[u]][i];//自己儿子的nxt等于自己nxt的儿子,这句话和“自己的nxt是,自己父亲的nxt的和它名字相同的儿子”的意思相同,只是主语从待更新节点变成已就更新节点。
Q.push(tree[u][i]);
}
}
}
return ;
}
inline void search(){
int ans=;
int u=;
int l=strlen(t);
REP(i,,l-){//循环遍历主串
int c=calc(t[i]);//计算这个字符的ACCII码
int k=tree[u][c];
while(k>){//对于每一个u遍历它的nxt,直到根
if(isend[k]){
ans+=isend[k];//加上isend,记录答案
isend[k]=;
}
k=nxt[k];
}
u=tree[u][c];//遍历到它的儿子。
}
printf("%d\n",ans);
}
int main(){
in(T);
while(T--){
total=;
cl(nxt);
cl(tree);
cl(isend);
in(n);
REP(i,,n){
scanf("%s",in);
insert(in);
}
scanf("%s",t);
getnxt();
search();
}
return ;
}
附加:可持久化AC自动机
如果你希望每当你查找到一个字符串,然后要把它删去时,就需要可持久化AC自动机。其实和普通的AC自动机很像,唯一区别是查找的时候去掉了对于每一个u遍历nxt直到根的步骤,然后让每个u都压进栈,遇到end就弹出栈里面此字符串长度的元素。
AC自动机详解(附加可持久化AC自动机)的更多相关文章
- [转] AC自动机详解
转载自:http://hi.baidu.com/nialv7/item/ce1ce015d44a6ba7feded52d AC自动机详解 AC自动机是用来处理多串匹配问题的,即给你很多串,再给你一篇文 ...
- Aho-Corasick 多模式匹配算法、AC自动机详解
Aho-Corasick算法是多模式匹配中的经典算法,目前在实际应用中较多. Aho-Corasick算法对应的数据结构是Aho-Corasick自动机,简称AC自动机. 搞编程的一般都应该知道自动机 ...
- AC自动机详解
概述 AC自动机全称Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法. 考虑这样一个场景,给出L个模式字符串(加总长度为N),以及长度为M大文本, ...
- 【转】AC算法详解
原文转自:http://blog.csdn.net/joylnwang/article/details/6793192 AC算法是Alfred V.Aho(<编译原理>(龙书)的作者),和 ...
- AC自动机详解 (P3808 模板)
AC自动机笔记 0.0 前言 哇,好久之前就看了 KMP 和 Trie 树,但是似乎一直没看懂 AC自动机?? 今天灵光一闪,加上之前看到一些博客和视频,瞬间秒懂啊... 其实这个玩意还是蛮好理解的. ...
- 详解Redis RDB持久化、AOF持久化
1.持久化 1.1 持久化简介 持久化(Persistence),持久化是将程序数据在持久状态和瞬时状态间转换的机制,即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘). 1.2 red ...
- Redis 详解 (七) AOF 持久化
目录 1.AOF简介 2.AOF 配置 3.开启 AOF 4.AOF 文件恢复 5. AOF 重写 6.AOF的优缺点 上一篇文章我们介绍了Redis的RDB持久化,RDB 持久化存在一个缺点是一定时 ...
- Redis 详解 (六) RDB 持久化
目录 1.RDB 简介 2.触发方式 ①.自动触发 ②.手动触发 3.恢复数据 4.停止 RDB 持久化 5.RDB 的优势和劣势 6.RDB 自动保存的原理 前面我们说过,Redis 相对于 Me ...
- 详解 ZooKeeper 数据持久化
本文作者:HelloGitHub-老荀 Hi,这里是 HelloGitHub 推出的 HelloZooKeeper 系列,免费开源.有趣.入门级的 ZooKeeper 教程,面向有编程基础的新手. 项 ...
随机推荐
- eclipse启动项目
今天做的任务不多,没有自己写代码,上午看了些文章,下午我司后台给配了配项目环境,全装C盘了..以后有我好受的.. 看着后台操作,修改了N多配置,tomcat.redis.zkServer..Nginx ...
- 环境变量配错了 command not found
一般就是忘记在PATH 前面加$ 1.可以用whereis或者which命令查看一下有没有这个命令 具体执行which lswhereis ls 2.系统环境变量导致的问题解决方案: exportPA ...
- 事务的特性——ACID
在日常操作中,对于一组相关操作通常需要其全部成功或全部失败.在关系型数据库中,这组操作称作为事务.事务具有四种特性:原子性,一致性,隔离性和持久性. 原子性(atomicity):事务必须以一个整体单 ...
- springBoot单元测试-模拟MVC测试
1)模拟mvc测试,和基础测试是一样的, 都需要在pom文件中引入junit的支持. 略 2)编写测试类 Application1TestMVC 在类头上除啦加入之前的@RunWith(SpringR ...
- Dubbo使用
[注:本文参考<Dubbo入门---搭建一个最简单的Demo框架>,感谢原创作者的知识探索与奉献] 一.Dubbo背景和简介 Dubbo开始于电商系统,因此在这里先从电商系统的演变讲起. ...
- windows下phpstrom中xdebug的使用
https://laravel-china.org/articles/16425/windows-phpstorm-xdebug-breakpoint-debugging
- 查看wtmp(登陆信息的内容)
/var/log/wtmp文件的作用 /var/log/wtmp也是一个二进制文件,记录每个用户的登录次数和持续时间等信息. 查看方法: 可以用last命令输出当中内容: debi ...
- C++面试总结
1.多态 C++多态分两种--静态和动态,其中静态联编支持的多态称为编译时多态,包括重载和模板:动态联编支持的多态称为运行时多态,包括 继承和虚函数实现. 多态主要是由虚函数实现的,虚函数 ...
- STL中stack/queue/map以及Boost unordered_map 的使用方法
一.stackstack 模板类的定义在<stack>头文件中.stack 模板类需要两个模板参数,一个是元素类型,一个容器类型,但只有元素类型是必要的,在不指定容器类型时,默认的容器类型 ...
- Source Insight 4.0 文件类型、编码格式、tab转空格、tab键自动补全设置。。。
1.编码格式 -- 在 Options->Preferences->Files 中的最下面,Default enconding 为 UTF-8 2.tab转空格 其他相关设置如下: 以下 ...