AC自动机是一个多模字符串匹配的自动机(网上说的),主要作用是在一个长串中同时进行多个字符串的匹配

基础芝士:

trie树(字典树)

烤馍片kmp单模字符串匹配

如果不会的建议去网上学一下(本篇讲解略过)

这里重点讲一讲AC自动机

(由于本蒟蒻不会指针,所以所有算法一律不使用指针,请神犇们谅解)

例:luogu3796 AC自动机(加强版)

其实AC自动机就是在trie树上构造KMP的next指针(在AC自动机中叫fail指针),然后进行匹配

举个例子:

模式串:

abab

abb

bab

匹配串:

aaabbbabababbba

AC自动机第一步:建立trie树!

建树过程略,反正建起的树长这样:

建树代码如下,基本和trie树代码接近:

void buildtree(char *p)
{
int l=strlen(p);
int now=0;
for(int i=0;i<l;i++)
{
int t=p[i]-'a'+1;
if(tree[now].to[t]==0)
{
tree[now].to[t]=++cnt;
tree[tree[now].to[t]].fa=now;
tree[tree[now].to[t]].ca=t;
}
now=tree[now].to[t];
}
tree[now].ed++;
}

接下来我们考虑构造fail指针

fail指针的含义其实就是:如果在这一位上失配了,那么整个串不必从头开始,而是直接从中间的某处开始继续在失配处匹配即可

由于这是一棵trie树,所以我们可以考虑基于bfs进行构造

首先,如果一开始就失配,那就没啥可说的了,直接返回最大的根节点,所以在构造trie树时一般从1开始,0作为虚节点为根

代码如下:

queue <int> M;
for(int i=1;i<=26;i++)
{
if(tree[0].to[i])
{
M.push(tree[0].to[i]);
tree[tree[0].to[i]].fall=0;
}
}

接下来,我们就可以进行bfs了

这里也是整个AC自动机中最复杂的地方

对于每个点,我们枚举他的每一个to指针,然后分类讨论:

①:这个to节点存在

(什么叫存在?比如上面的trie树,根据字符集来讲,每个节点都应该有两个儿子,可是事实上大部分节点都只有一个儿子,那么有的这个儿子就叫存在,没有就叫不存在)

那么,这个to的fail指针应该指向他父节点的fail指针指向节点所指向的对应的to(读二十遍)

先放代码,再解释,否则不好懂

if(tree[u].to[i])
{
tree[tree[u].to[i]].fall=tree[tree[u].fall].to[i];
M.push(tree[u].to[i]);
}

解释一下,就像这样:

其中蓝色的线为fail指针

发现什么了吗?

一个点fail指针所指向的点所在字符串的前缀一定是这个点所在字符串的子串!

举个例子:

如图所示,右边红色框里的字符串的前缀是左边红色字符串的一个子串,因为左边的b指向了右边的b

(当然,这个前缀理论仅适用于fail指针指向的节点之前的前缀,而之后的是无法保证的)

但是我们会发现一个bug:看到第二个串的最后一个b了吗?他的fail指针应该指向他父节点的fail指针指向节点的对应节点,可是..没有这个节点啊...

直接指回根节点?

这不太好

因为明明有能匹配上的啊

所以我们要利用trie图思想了。

trie图与AC自动机少数的不同就是trie图会补全所有的子节点,补全方法是指向这个点父节点的fail指针指向节点的对应节点

else
{
tree[u].to[i]=tree[tree[u].fall].to[i];
}

所以这也就是上面所述的分类讨论的第二种情况:如果这个节点不存在,那么要把这个节点的指针建起来

这样就可以指了

最后构造好的fail指针长这样:

其中绿色的是特殊构造出来的fail指针

fail指针都完事了,接下来就好办了。

我们将模式串在这个AC自动机上跑

查询操作:

int query(char *p)
{
int l=strlen(p);
int ans=0;
tot=0;
int now=0;
for(int i=0;i<l;i++)
{
int t=p[i]-'a'+1;
now=tree[now].to[t];
int temp=now;
while(temp)
{
if(tree[temp].ed>ans)
{
memset(ret,0,sizeof(ret));
tot=0;
ret[++tot]=temp;
ans=tree[temp].ed;
}else if(tree[temp].ed==ans)
{
ret[++tot]=temp;
}
if(tree[temp].ed)
{
tree[temp].ed++;
}
temp=tree[temp].fall;
}
}
return ans;
}

稍微解释一下,就是顺着trie树跑匹配串,根据上文所述fail指针的性质,每次向前找一个前缀使得这个前缀是这个匹配串的子串,于是我们总是能找到整个串是这个字符串的子串

还有一步操作很重要,即上面的最后一个if,这一步的操作目的在于累计某个串被匹配上的次数

这样就完事了

贴代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
struct Trie
{
int to[27];
int fa;
int fall;
int ca;
int ed;
}tree[1000005];
int ret[155];
char s[1000005];
int cnt=0;
int tot=0;
void buildtree(char *p)
{
int l=strlen(p);
int now=0;
for(int i=0;i<l;i++)
{
int t=p[i]-'a'+1;
if(tree[now].to[t]==0)
{
tree[now].to[t]=++cnt;
tree[tree[now].to[t]].fa=now;
tree[tree[now].to[t]].ca=t;
}
now=tree[now].to[t];
}
tree[now].ed++;
}
void getfail()
{
queue <int> M;
for(int i=1;i<=26;i++)
{
if(tree[0].to[i])
{
M.push(tree[0].to[i]);
tree[tree[0].to[i]].fall=0;
}
}
while(!M.empty())
{
int u=M.front();
M.pop();
for(int i=1;i<=26;i++)
{
if(tree[u].to[i])
{
tree[tree[u].to[i]].fall=tree[tree[u].fall].to[i];
M.push(tree[u].to[i]);
}else
{
tree[u].to[i]=tree[tree[u].fall].to[i];
}
}
}
}
int query(char *p)
{
int l=strlen(p);
int ans=0;
tot=0;
int now=0;
for(int i=0;i<l;i++)
{
int t=p[i]-'a'+1;
now=tree[now].to[t];
int temp=now;
while(temp)
{
if(tree[temp].ed>ans)
{
memset(ret,0,sizeof(ret));
tot=0;
ret[++tot]=temp;
ans=tree[temp].ed;
}else if(tree[temp].ed==ans)
{
ret[++tot]=temp;
}
if(tree[temp].ed)
{
tree[temp].ed++;
}
temp=tree[temp].fall;
}
}
return ans;
}
bool cmp(int a,int b)
{
return a<b;
}
void init()
{
memset(ret,0,sizeof(ret));
memset(tree,0,sizeof(tree));
cnt=0;
tot=0;
}
void print(int rt)
{
if(!rt)
{
return;
}
print(tree[rt].fa);
printf("%c",tree[rt].ca-1+'a');
}
int main()
{
int n;
while(1)
{
scanf("%d",&n);
if(n==0)
{
return 0;
}
init();
for(int i=1;i<=n;i++)
{
scanf("%s",s);
buildtree(s);
}
getfail();
scanf("%s",s);
printf("%d\n",query(s));
sort(ret+1,ret+tot+1,cmp);
for(int i=1;i<=tot;i++)
{
print(ret[i]);
printf("\n");
}
}
return 0;
}

AC自动机(trie图版)的更多相关文章

  1. 【AC自动机&&Trie图】积累

    以前KMP和后缀系列(主要是后缀数组,后缀自动机),都刷了一定数量的题,但是对于AC自动机,却有些冷落,罪过. 但是我感觉,在蓝桥杯比赛中AC自动机出现的概率比后缀系列大,简单的会考匹配,稍难一点会考 ...

  2. 小菜鸟 菜谈 KMP->字典树->AC自动机->trie 图 (改进与不改进)

    本文的主要宗旨是总结自己看了大佬们对AC自动机和trie 图 的一些理解与看法.(前沿:本人水平有限,总结有误,希望大佬们可以指出) KMP分割线--------------------------- ...

  3. AC自动机讲解

    今天花了半天肝下AC自动机,总算啃下一块硬骨头,熬夜把博客赶出来.. 正如许多博客所说,AC自动机看似很难很妙,而事实上不难,但的确很妙.笼统地说,AC自动机=Trie+KMP,但是仅仅知道这个并没有 ...

  4. 洛谷P2444 [POI2000]病毒(AC自动机,DFS求环)

    洛谷题目传送门 AC自动机入门--yyb巨佬的博客 AC自动机入手经典好题(虽然年代久远) 有了fail指针,trie树就不是原来的树型结构了,我们可以把它叫做trie图,由父节点向子节点连的边和fa ...

  5. 「AC自动机」学习笔记

    AC自动机(Aho-Corasick Automaton),虽然不能够帮你自动AC,但是真的还是非常神奇的一个数据结构.AC自动机用来处理多模式串匹配问题,可以看做是KMP(单模式串匹配问题)的升级版 ...

  6. BZOJ1559[JSOI2009]密码——AC自动机+DP+搜索

    题目描述 输入 输出 样例输入 10 2 hello world 样例输出 2 helloworld worldhello 提示 这题算是一个套路题了,多个串求都包含它们的长为L的串的方案数. 显然是 ...

  7. 【模板】AC自动机(简单版)

    我:“woc...AC自动机?” 我:“可以自动AC???” 然鹅... 大佬:“傻...” 我:“(⊙_⊙)?” 大佬:“缺...” 我:“......” (大佬...卒 | 逃...) emm.. ...

  8. bzoj 2754 ac自动机

    第一道AC自动机题目. 记一下对AC自动机的理解吧: AC自动机=Trie+KMP.即在Trie上应用KMP思想,实现多Pattern的匹配问题. 复杂度是预处理O(segma len(P)),匹配是 ...

  9. AC自动机--summer-work之我连模板题都做不出

    这章对现在的我来说有点难,要是不写点东西,三天后怕是就一无所有了. 但写这个没有营养的blog的目的真的不是做题或提升,只是学习学习代码和理解一些概念. 现在对AC自动机的理解还十分浅薄,这里先贴上目 ...

随机推荐

  1. LVM基本概念及工作原理

    LVM基本概念及工作原理 背景知识: 一直困惑于LVM,特地找资料查了查,终于对LVM的概念和工作原理有了深入的理解.接下来记录下.新的技术出来必定是为了改变现有的不足,所以LVM的出现是由于对现有磁 ...

  2. 函数和常用模块【day06】:subprocess模块(十)

    本节内容 1.概述 2.前言 3.subprocess模块 4.subprocess.Popen() 一.概述 我们在实际的工作中,需要跟操作系统的命令做交互,但我们如何用python去跟操作系统之间 ...

  3. 标准遗传算法(实数编码 python实现)模拟二进制交叉SBX 多项式变异

    代码地址: https://github.com/guojun007/real_sga 本部分是采用实数编码的标准遗传算法,整体流程与上一篇二进制编码的基本一致, 主要区别在于本部分的交叉操作为模拟二 ...

  4. 跳表,Redis 为什么用跳表而不用平衡树?

    https://juejin.im/post/57fa935b0e3dd90057c50fbc 在 Redis 中,list 有两种存储方式:双链表(LinkedList)和压缩双链表(ziplist ...

  5. python 小爬虫爬取博客文章初体验

    最近学习 python 走火入魔,趁着热情继续初级体验一下下爬虫,以前用 java也写过,这里还是最初级的爬取html,都没有用html解析器,正则等...而且一直在循环效率肯定### 很低下 imp ...

  6. python 小程序,在列表中找到所有指定内容的位置

    要求如下图所示:

  7. DotNetBar SuperTabStrip带图标时调整为指定字号的最小宽度

    SuperTabStrip带图标时很占空间,需要调整1.整体设置 2.单个Tab设置

  8. Easyui comboxgrid弹出窗增加搜索功能

    效果如上图所示 <input id="q_item" name="q_item" style="width:200px" toolba ...

  9. 20155228 2016-2017-2 《Java程序设计》第7周学习总结

    20155228 2016-2017-2 <Java程序设计>第7周学习总结 教材学习内容总结 Lambda 方法参考的特性,在重用现有的API上扮演了重要的角色.重用现有方法操作,可以避 ...

  10. Linux 查看文件编码

    查看某个文件的编码格式:使用 vi 编辑器 打开文件: 按 Esc 输入 ” : set fileencoding “ 就会显示出来 文件的编码格式 : set fileencoding