AC自动机是用来干什么的:

  AC自动机是用来解决多模匹配问题,例如有单词s1,s2,s3,s4,s5,s6,问:在文本串ss中有几个单词出现过,类似。

AC自动机实现这个功能需要三个部分:

1、将所有单词用字典树的方法建树

2、构建失配指针

3、在文本串中的查找函数

这里主要讲2和3

一、建树

int tree[][],vis[],fail[];
int t,n,cnt,id,root,num=;
string s,ss; void insert()//建树
{
root=;
for(int i=;s[i];i++)
{
id=s[i]-'a';
if(tree[root][id]==)
tree[root][id]=++num;
root=tree[root][id];
}
vis[root]++;//单词结尾标记
}

二、构建失配指针

失配指针的作用是:当文本串在当前节点失配后,我们应该到哪个节点去继续匹配

失配指针起作用之后,可以求到在文本串中长度为[0,当前节点位置]的字串的最长公共后缀

如何构建失配指针:

显然,我们要做的就是快速地求出所有点的fail指针。我们以bfs的顺序依次求出每个节点的fail,这样,当我们要求一个节点的fail时,它的父亲的fail肯定已经求出来了。若当前节点为A,其父节点为B,B的fail为C,那么C所代表的字符串一定是B的最长的后缀。如果C有一个儿子D的字符与A的字符等同,那么显然D所代表的串(C加一个字符)就是A所代表的串(B加一个字符)的最长后缀。如果C没有一个儿子,使其字符与A的字符等同呢?很简单,只需要再访问C的fail就行了。如此反复,直到A的最长后缀找到,或者A的fail指向根节点为止。(A在Trie树中没有后缀,乖乖回到根重新匹配吧!)

步骤:

  1. 为了少一些特判,设置一个辅助根节点0号节点,0号节点的所有儿子都指向真正的根节点1号节点,然后将1号节点的fail指向0号节点。
  2. 找到2号节点的父亲节点的fail节点0号节点,看0号节点有没有为a的子节点。有,于是2号节点的fail指向1号节点。
  3. 找到3号节点的父亲节点的fail节点0号节点,看0号节点有没有为b的子节点。有,于是3号节点的fail指向1号节点。
  4. 找到4号节点的父亲节点的fail节点1号节点,看1号节点有没有为b的子节点。有,于是4号节点的fail指向3号节点。
  5. 同上。
  6. 同上。
  7. 同上。
  8. 找到8号节点的父亲节点的fail节点5号节点,看5号节点有没有为b的子节点。没有,于是再找到5号节点的fail节点2号节点,看2号节点有没有为b的子节点。有,于是8号节点的fail指向4号节点。

代码:

void build()//构建失配指针
{
queue<int>p;
for(int i=;i<;i++)
{
if(tree[][i])//将第二行所有出现过的字母的失配指针指向root节点0
{
fail[tree[][i]]=;
p.push(tree[][i]);
}
} while(!p.empty())
{
root=p.front();
p.pop();
for(int i=;i<;i++)
{
if(tree[root][i]==)//没有建树,不存在这个字母
continue;
p.push(tree[root][i]);
int fa=fail[root];//fa是父亲节点
while(fa&&tree[fa][i]==)//fa不为0,并且fa的子节点没有这个字母
fa=fail[fa];//继续判断fa的父亲节点的子节点有没有这个字母 fail[tree[root][i]]=tree[fa][i];//找到就构建失配指针 }
}
}

三、查找函数

for循环遍历一遍文本串,统计被标记的次数,记录最终答案

这里要注意的是,失配指针不仅仅是在失配的时候起作用

为了不让这种事情发生,我们每遇到一个fail指针就必须进行“失配”转移,以保证不会漏过任何一个子串,就像这样:

代码:

int search(string ss)//查找
{
root=,cnt=;
for(int i=;ss[i];i++)
{
id=ss[i]-'a';
while(root&&tree[root][id]==)//失配转移
root=fail[root]; root=tree[root][id];
int temp=root;
while(vis[temp])
{
cnt=cnt+vis[temp];
vis[temp]=;//清除标记,避免重复
temp=fail[temp];
}
}
return cnt;
}

优化

朴素的AC自动机的时间复杂度是不行的,原因如下:

匹配时因为每次都要跳fail边,复杂度上界可以达到 O(ml)

而Tire图就是用来解决这种问题的。可以想到,匹配时跳fail边是十分浪费时间的。举个例子,对于字符集{a,b,c}上的模式ab,aab,aaab,aaaab,ac和文本串aaaac,它们建出来的AC自动机和匹配过程是这样的(蓝色边是Trie树的边,红色边是fail指针,黄色边是匹配时的状态转移):

我们会想,如果失配时可以一步到位就好了。每次跳fail边的过程是固定的:一直跳,直到找到拥有儿子c的节点为止。也就是说,无论什么时候在这个节点上失配,只要你找的是字符c,你总会在固定的节点上重新开始匹配。既然这样,不如直接把那个字符为c的节点变成自己的儿子,就可以省去跳fail边的麻烦:

上图中,所有的节点的a,b,c三个子节点都是满的(未画出的边都指向根节点,表示完全失配只能从根重新开始)。这样,原本是DAG结构的AC自动机上出现了环,这样的结构我们称之为Trie图。于是乎,在匹配的时候我们终于可以不用考虑fail边,一口气不停地匹配到底辣٩(๑>◡<๑)۶复杂度变成了真正的 O(m)O(m)

void build()
{
queue<int>p;
fail[]=;
for(int i=;i<;i++)
{
if(tree[][i]!=)
{
fail[tree[][i]]=;
last[tree[][i]]=;
p.push(tree[][i]);
}
}
while(!p.empty())
{
root=p.front();
p.pop();
for(int i=;i<;i++)
{
if(tree[root][i]==)
{
tree[root][i]=tree[fail[root]][i];
continue;
}
p.push(tree[root][i]);
int temp=tree[root][i];
int fa=fail[root];
fail[temp]=tree[fa][i];
last[temp]=(vis[fail[temp]]?fail[temp]:last[fail[temp]]);
}
}
}

last优化

上述方法将建图+匹配的复杂度成功优化为了 O(∑n+m)O(∑n+m) ,但是别忘了,匹配成功时的计数也是需要跳fail边的。然而,为了跳到一个结束节点,我们可能需要中途跳到很多没用的伪结束节点:

如果一个节点的fail指向一个结尾节点,那么这个点也成为一个(伪)结尾节点。在匹配时,如果遇到结尾节点,就进行相应的计数处理。

这里面就又有优化的余地了:对于不是真正结束节点的伪结束点,直接跳过它就好了。我们用一个last指针表示“在它顶上的fail边所指向的一串节点中,第一个真正的结束节点”。于是,每次计数处理时,我们不跳fail边,改为跳last边,省去了很多冗余操作。

获得last指针的方法也十分简单,就是在void build()中加一句话:

last[temp]=(vis[fail[temp]]?fail[temp]:last[fail[temp]]);

然后匹配时的代码就变成了:

void count(int x)
{
if(x!=)
{
ans[vis[x]]++;
count(last[x]);
}
} void search()
{
root=;
for(int i=;ss[i];i++)
{
id=ss[i];
while(root&&tree[root][id]==)
root=fail[root];
root=tree[root][id];
if(vis[root])
count(root);
else if(last[root])
count(last[root]);
}
}

以上转载自https://www.cnblogs.com/sclbgw7/p/9875671.html,谢谢博主

模板题:https://www.cnblogs.com/-citywall123/p/11300251.html

优化模板题:https://www.cnblogs.com/-citywall123/p/11319272.html

AC自动机 (模板)的更多相关文章

  1. HDU 2222 AC自动机模板题

    题目: http://acm.hdu.edu.cn/showproblem.php?pid=2222 AC自动机模板题 我现在对AC自动机的理解还一般,就贴一下我参考学习的两篇博客的链接: http: ...

  2. Match:Keywords Search(AC自动机模板)(HDU 2222)

    多模匹配 题目大意:给定很多个字串A,B,C,D,E....,然后再给你目标串str字串,看目标串中出现多少个给定的字串. 经典AC自动机模板题,不多说. #include <iostream& ...

  3. HDU 3065 (AC自动机模板题)

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=3065 题目大意:多个模式串,范围是大写字母.匹配串的字符范围是(0~127).问匹配串中含有哪几种模 ...

  4. HDU 2896 (AC自动机模板题)

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=2896 题目大意:多个模式串.多个匹配串.其中串的字符范围是(0~127).问匹配串中含有哪几个模式串 ...

  5. HDU 2222(AC自动机模板题)

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=2222 题目大意:多个模式串.问匹配串中含有多少个模式串.注意模式串有重复,所以要累计重复结果. 解题 ...

  6. HDU 2222 (AC自动机模板题)

    题意: 给一个文本串和多个模式串,求文本串中一共出现多少次模式串 分析: ac自动机模板,关键是失配函数 #include <map> #include <set> #incl ...

  7. hdu 2222 Keywords Search ac自动机模板

    题目链接 先整理一发ac自动机模板.. #include <iostream> #include <vector> #include <cstdio> #inclu ...

  8. KMP与AC自动机模板

    HDU 1711 Number Sequence(KMP模板题) http://acm.hdu.edu.cn/showproblem.php?pid=1711 #include<bits/std ...

  9. HDU3695(AC自动机模板题)

    题意:给你n个字符串,再给你一个大的字符串A,问你着n个字符串在正的A和反的A里出现多少个? 其实就是AC自动机模板题啊( ╯□╰ ) 正着query一次再反着query一次就好了 /* gyt Li ...

  10. POJ2222 Keywords Search AC自动机模板

    http://acm.hdu.edu.cn/showproblem.php?pid=2222 题意:给出一些单词,求多少个单词在字符串中出现过(单词表单词可能有相同的,这些相同的单词视为不同的分别计数 ...

随机推荐

  1. hive内表和外表的创建、载入数据、区别

    创建表 创建内表 create table customer( customerId int, firstName string, lastName STRING, birstDay timestam ...

  2. RFID学习

    RFID:射频识别卡有读卡器和电子标签(有源/无源/半有源)组成,工作频率有LF/HF/UHF/MF,低频的通讯距离近(1m)常用来做门禁:高频的通讯距离远可以批量读取. 无源的轻巧/防拆一致性好,在 ...

  3. 「国家集训队」middle

    「国家集训队」middle 传送门 按照中位数题的套路,二分答案 \(mid\),序列中 \(\ge mid\) 记为 \(1\),\(< mid\) 的记为 \(-1\) 然后只要存在一个区间 ...

  4. December 21st, Week 51st Saturday, 2019

    May the odds be ever in your favor. 愿好运永远眷顾你. From The Hunger Games. May we all have good luck, and ...

  5. BUUCTF知识记录

    [强网杯 2019]随便注 先尝试普通的注入 发现注入成功了,接下来走流程的时候碰到了问题 发现过滤了select和where这个两个最重要的查询语句,不过其他的过滤很奇怪,为什么要过滤update, ...

  6. Linux操作系统服务器学习笔记一

    初识Linux: Linux 是什么? Linux是一套免费使用和自由传播的类Unix操作系统,是一个多用户.多任务.支持多线程和多CPU的操作系统.它能运行主要的UNIX工具软件.应用程序和网络协议 ...

  7. MySQL之可视化软件

    MySQL可视化客户端 多款可视化界面,总有一款适合你 个人推荐使用MySQL官方提供的可视化工具. 下载链接 使用方法链接

  8. Java中四种遍历Map对象的方法

    方法一:在for-each循环中使用entry来遍历,通过Map.entrySet遍历key和value,这是最常见的并且在大多数情况下也是最可取的遍历方式.在键值都需要时使用. Map<Int ...

  9. Android音频捕获(录音)(转)

    原文:http://www.yiibai.com/android/android_audio_capture.html Android有一个内置的麦克风,通过它可以捕获音频和存储,或在手机进行播放.有 ...

  10. 什么是Nib文件

    Nib文件是一种特殊类型的资源文件,它用于保存iPhone OS或Mac OS X应用程序的用户接口.Nib文件是Interface Builder文档.通常您会使用Interface Builder ...