简单版AC自动机
简单版\(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自动机的更多相关文章
- 简易版AC自动机
为什么说是简易版? 因为复杂度大概是\(O(M*\overline N)\),而似乎还有另一种大概是\(O(M+\sum N)\)的. 不过据说比赛不会卡前一种做法,因为模式串一般不会很长. 那么步入 ...
- java版AC自动机
class Trie { int [][]Next=new int[500005][128]; int []fail=new int[500005]; int []end=new int[500005 ...
- 模板】AC自动机(简单版)
模板]AC自动机(简单版) https://www.luogu.org/problemnew/show/P3808 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保 ...
- 【模版】AC自动机(简单版)
题目背景 这是一道简单的AC自动机模版题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 题目描述 给定n个模式串和1个文本串,求有多少个模式串在文本 ...
- 洛谷P3808 【模板】AC自动机(简单版)
题目背景 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 管理员提示:本题数据内有重复的单词,且重复单词应该计算多次, ...
- 【模板】AC自动机(简单版)
我:“woc...AC自动机?” 我:“可以自动AC???” 然鹅... 大佬:“傻...” 我:“(⊙_⊙)?” 大佬:“缺...” 我:“......” (大佬...卒 | 逃...) emm.. ...
- 【刷题】洛谷 P3808 【模板】AC自动机(简单版)
题目背景 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 管理员提示:本题数据内有重复的单词,且重复单词应该计算多次, ...
- P3808 【模板】AC自动机(简单版)
题目背景 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 管理员提示:本题数据内有重复的单词,且重复单词应该计算多次, ...
- luogu P3808 【模板】AC自动机(简单版)
题目背景 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 管理员提示:本题数据内有重复的单词,且重复单词应该计算多次, ...
随机推荐
- php学习--变量和数据类型
PHP变量 变量 程序执行期间,可以变化的量即为变量. 声明变量 以美元$ 符号声明 注意:(PHP严格区分大小写) 变量名称以 字母.或下划线开始,后面跟上数字/字母/下划线,不能包含特殊字符 ...
- Appengine直接下载文件并保存到google drive
一直对下载文件比较感兴趣.前些日子无意搜到google 推出一项服务,可以直接将文件下载到google drive中,原型猛戳这里,但有限额限制.一时脑洞大开,可不可以在appengine 上架设服务 ...
- JVM新生代老年代详解
1.为什么会有年轻代 我们先来屡屡,为什么需要把堆分代?不分代不能完成他所做的事情么?其实不分代完全可以,分代的唯一理由就是优化GC性能.你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我 ...
- Linux下lshw,lsscsi,lscpu,lsusb,lsblk硬件查看命令
Linux下lshw,lsscsi,lscpu,lsusb,lsblk硬件查看命令 2016-12-14 何敏杰 1条评论 544次浏览 注意:如有提示命令找不到command not found ...
- Scrum立会报告+燃尽图(十二月七日总第三十八次):功能测试
此作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2284 项目地址:https://git.coding.net/zhang ...
- Final发布中间产物
目录 ❶版本控制 ❷软件功能说明书 ❸WBS ❹PSP 一.版本控制 ①Git地址:https://git.coding.net/tianjiping/Android-tianjiping.git ② ...
- WebGL学习笔记五
本章主要是对纹理的进一步讲解,我们很多时候需要将现实中已有 的图片在网页中展示出来而不是去创造图片,通过纹理 我们可以将光栅化的图形和图片纹理形成映射并且将图片在图形 中显示出来.基本过程与前几章一致 ...
- 项目Beta冲刺(团队)第七天
1.昨天的困难 服务器部署出了问题,本地服务器差点崩掉 运行一直闪退,在查找哪里出现问题的路上一去不复返 2.今天解决的进度 成员 进度 陈家权 消息功能模块 赖晓连 问答功能模块 雷晶 部署服务器到 ...
- 网页访问过程(基于CDN)
1. 全局负载均衡(基于DNS) 如果有多台 WEB 服务器同时为一个域名提供服务时,即一条 URL 对应多个 IP 地址,那么该 URL 的权威域名服务器可能会根据该 URL 解析出多个 IP 地址 ...
- JS贪吃蛇小游戏
效果图展示: 具体实现代码如下: (1)html部分 !DOCTYPE html> <html> <head> <meta charset="utf-8& ...