简单版\(AC\)自动机

学之前听别人说起一直以为很难,今天学了简单版的\(AC\)自动机,感觉海星,只要理解了\(KMP\)一切都好说。

前置知识:\(KMP\)(有链接)

前置知识:\(Trie\)树

字典树(\(Trie\)树)比较简单,就是把许多个单词通过树连接起来。每个点记录一下儿子个数以及是否是单词结尾即可。每次加入一个单词时,从第一个字母开始搜索,如果当前字母存在,就从该字母的儿子里找下一个字母,否则就新建一个节点,直到把这个单词全部加入进去,然后在最后的字符上标记一下表示以这个字母结尾的单词多了一个。

那么\(AC\)自动机实际上就是将两者合并了起来,在字典树上进行\(KMP\)。

先说一下\(AC\)自动机是干什么的。一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。要搞懂AC自动机,先得有模式树(字典树)\(Trie\)和\(KMP\)模式匹配算法的基础知识。\(KMP\)算法是单模式串的字符匹配算法,\(AC\)自动机是多模式串的字符匹配算法。

说白了就是给你一堆字符串,然后再给你一个字符串,问最后这个字符串中出现了多少个前面给出的字符串。

首先我们要有一个字典树。对于给出的那一堆字符串,我们要一个一个加到树里。代码如下:

struct AC{//字典树
int end,vis[26],fail;//vis表示儿子的编号
}AC[1000006];
int cnt;
void Build(string s){//要加入的单词
int l=s.length(),now=0;//now是当前节点
for(int i=0;i<l;++i){
if(AC[now].vis[s[i]-'a']==0) AC[now].vis[s[i]-'a']=++cnt;
//如果这个字母没有,就新建一个
now=AC[now].vis[s[i]-'a'];//如果有或者已经建好,就往下跳
}
AC[now].end++;//在最后一个字母处标记有几个单词以它结尾
}

有了字典树,考虑怎样在树上进行\(KMP\)

在\(KMP\)里面的\(next\)指针在这里改成\(fail\),其实都一样。

每个节点\(t\)有\(fail\)指针,其所指向的节点和\(t\)节点的字符是一样的。因为如果\(t\)匹配成功,而\(t\)的儿子匹配失败,那么需要从\(t\)的\(fail\)指针的儿子节点开始匹配。

\(fail\)指针用\(BFS\)来求。

首先,根节点的\(fail\)指针显然指向他自己,即\(0\)。而他的儿子,也就是深度为一的节点的指针也是指向他的。那么考虑剩下的节点\(t\)。它的父亲节点的\(fail\)指针已经知道,那么这个指针指向的节点假如是\(u\)的话,如果\(u\)有一个和\(t\)一样的节点,那么\(t\)的\(fail\)指针就应该指向它,如果没有,就要从\(father->fail->fail\)里找,直到找到相同的节点或者到根节点。也就是说要顺着之前的失配指针走一遍,有点麻烦。

考虑如果当前节点没有某个字母,那么我们可以将该节点指向这个字母的指针,指到他的失配指针指向的节点的这个字母上。

if(AC[u].vis[i]==0)
AC[u].vis[i]=AC[AC[u].fail].vis[i];

这样就不用沿着失配指针走一遍了。代码如下:

void Get_fail(){
queue<int>Q;//队列,bfs
for(int i=0;i<26;++i)//处理深度为二的点
if(AC[0].vis[i]) AC[AC[0].vis[i]].fail=0,Q.push(AC[0].vis[i]);
while(!Q.empty()){
int u=Q.front();
for(int i=0;i<26;++i)
if(AC[u].vis[i]) AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i],Q.push(AC[u].vis[i]);
//如果有这个点,就直接更新指针并压入队列
else AC[u].vis[i]=AC[AC[u].fail].vis[i];//没有就按上述方法处理
Q.pop();
}
}

最后就是统计了。

对于每个字母,如果他是几个单词的结尾,那么久加上他的以及他的所有失配指针的答案,因为他可以,他的失配指针同样可以。

代码:

int AC_query(string s){
int l=s.length(),now=0,ans=0;
for(int i=0;i<l;++i){
now=AC[now].vis[s[i]-'a'];
for(int t=now;t&&AC[t].end!=-1;t=AC[t].fail)//沿着失配指针跳
ans+=AC[t].end,AC[t].end=-1;//统计答案,标记-1为了防止重复统计
}
return ans;
}

\(Code\)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
struct AC{
int end,vis[26],fail;
}AC[1000006];
int cnt;
void Build(string s){
int l=s.length(),now=0;
for(int i=0;i<l;++i){
if(AC[now].vis[s[i]-'a']==0) AC[now].vis[s[i]-'a']=++cnt;
now=AC[now].vis[s[i]-'a'];
}
AC[now].end++;
}
void Get_fail(){
queue<int>Q;
for(int i=0;i<26;++i)
if(AC[0].vis[i]) AC[AC[0].vis[i]].fail=0,Q.push(AC[0].vis[i]);
while(!Q.empty()){
int u=Q.front();
for(int i=0;i<26;++i)
if(AC[u].vis[i]) AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i],Q.push(AC[u].vis[i]);
else AC[u].vis[i]=AC[AC[u].fail].vis[i];
Q.pop();
}
}
int AC_query(string s){
int l=s.length(),now=0,ans=0;
for(int i=0;i<l;++i){
now=AC[now].vis[s[i]-'a'];
for(int t=now;t&&AC[t].end!=-1;t=AC[t].fail)
ans+=AC[t].end,AC[t].end=-1;
}
return ans;
}
int main(){
int n;
string s;
cin>>n;
for(int i=1;i<=n;++i) cin>>s,Build(s);
AC[0].fail=0;
Get_fail();
cin>>s;
cout<<AC_query(s);
return 0;
}

简单版AC自动机的更多相关文章

  1. 简易版AC自动机

    为什么说是简易版? 因为复杂度大概是\(O(M*\overline N)\),而似乎还有另一种大概是\(O(M+\sum N)\)的. 不过据说比赛不会卡前一种做法,因为模式串一般不会很长. 那么步入 ...

  2. java版AC自动机

    class Trie { int [][]Next=new int[500005][128]; int []fail=new int[500005]; int []end=new int[500005 ...

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

    模板]AC自动机(简单版) https://www.luogu.org/problemnew/show/P3808 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保 ...

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

    题目背景 这是一道简单的AC自动机模版题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 题目描述 给定n个模式串和1个文本串,求有多少个模式串在文本 ...

  5. 洛谷P3808 【模板】AC自动机(简单版)

    题目背景 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 管理员提示:本题数据内有重复的单词,且重复单词应该计算多次, ...

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

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

  7. 【刷题】洛谷 P3808 【模板】AC自动机(简单版)

    题目背景 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 管理员提示:本题数据内有重复的单词,且重复单词应该计算多次, ...

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

    题目背景 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 管理员提示:本题数据内有重复的单词,且重复单词应该计算多次, ...

  9. luogu P3808 【模板】AC自动机(简单版)

    题目背景 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 管理员提示:本题数据内有重复的单词,且重复单词应该计算多次, ...

随机推荐

  1. php学习--变量和数据类型

    PHP变量 变量 ​ 程序执行期间,可以变化的量即为变量. 声明变量 以美元$ 符号声明 注意:(PHP严格区分大小写) 变量名称以 字母.或下划线开始,后面跟上数字/字母/下划线,不能包含特殊字符 ...

  2. Appengine直接下载文件并保存到google drive

    一直对下载文件比较感兴趣.前些日子无意搜到google 推出一项服务,可以直接将文件下载到google drive中,原型猛戳这里,但有限额限制.一时脑洞大开,可不可以在appengine 上架设服务 ...

  3. JVM新生代老年代详解

    1.为什么会有年轻代 我们先来屡屡,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能.你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我 ...

  4. Linux下lshw,lsscsi,lscpu,lsusb,lsblk硬件查看命令

    Linux下lshw,lsscsi,lscpu,lsusb,lsblk硬件查看命令 2016-12-14 何敏杰 1条评论 544次浏览   注意:如有提示命令找不到command not found ...

  5. Scrum立会报告+燃尽图(十二月七日总第三十八次):功能测试

    此作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2284 项目地址:https://git.coding.net/zhang ...

  6. Final发布中间产物

    目录 ❶版本控制 ❷软件功能说明书 ❸WBS ❹PSP 一.版本控制 ①Git地址:https://git.coding.net/tianjiping/Android-tianjiping.git ② ...

  7. WebGL学习笔记五

    本章主要是对纹理的进一步讲解,我们很多时候需要将现实中已有 的图片在网页中展示出来而不是去创造图片,通过纹理 我们可以将光栅化的图形和图片纹理形成映射并且将图片在图形 中显示出来.基本过程与前几章一致 ...

  8. 项目Beta冲刺(团队)第七天

    1.昨天的困难 服务器部署出了问题,本地服务器差点崩掉 运行一直闪退,在查找哪里出现问题的路上一去不复返 2.今天解决的进度 成员 进度 陈家权 消息功能模块 赖晓连 问答功能模块 雷晶 部署服务器到 ...

  9. 网页访问过程(基于CDN)

    1. 全局负载均衡(基于DNS) 如果有多台 WEB 服务器同时为一个域名提供服务时,即一条 URL 对应多个 IP 地址,那么该 URL 的权威域名服务器可能会根据该 URL 解析出多个 IP 地址 ...

  10. JS贪吃蛇小游戏

    效果图展示: 具体实现代码如下: (1)html部分 !DOCTYPE html> <html> <head> <meta charset="utf-8& ...