AC自动机及其模板
模板
#include<queue> #include<stdio.h> #include<string.h> using namespace std; ; ; ; struct Aho{ struct StateTable{ int Next[Letter]; int fail, cnt; }Node[Max_Tot]; int Size; queue<int> que; inline void init(){ while(!que.empty()) que.pop(); memset(Node[].Next, , ].Next)); Node[].fail = Node[].cnt = ; Size = ; } inline void insert(char *s){ int len = strlen(s); ; ; i<len; i++){ int idx = s[i] - 'a'; if(!Node[now].Next[idx]){ memset(Node[Size].Next, , sizeof(Node[Size].Next)); Node[Size].fail = Node[Size].cnt = ; Node[now].Next[idx] = Size++; } now = Node[now].Next[idx]; } Node[now].cnt++; } inline void BuildFail(){ Node[].fail = -; que.push(); while(!que.empty()){ int top = que.front(); que.pop(); ; i<Letter; i++){ if(Node[top].Next[i]){ ) Node[ Node[top].Next[i] ].fail = ; else{ int v = Node[top].fail; ){ if(Node[v].Next[i]){ Node[ Node[top].Next[i] ].fail = Node[v].Next[i]; break; }v = Node[v].fail; }) Node[ Node[top].Next[i] ].fail = ; }que.push(Node[top].Next[i]); } } } } inline void Get(int u, int &res){ while(u){ res += Node[u].cnt; Node[u].cnt = ; u = Node[u].fail; } } int Match(char *s){ int len = strlen(s); , now = ; ; i<len; i++){ int idx = s[i] - 'a'; if(Node[now].Next[idx]) now = Node[now].Next[idx]; else{ int p = Node[now].fail; && Node[p].Next[idx]==) p = Node[p].fail; ) now = ; else now = Node[p].Next[idx]; } if(Node[now].cnt) Get(now, res); } return res; } }ac; char S[Max_Len]; int main(void) { // ac.init(); // ac.BuildFail(); // ac.Match(); // ..... ; }
#include<bits/stdc++.h> using namespace std; #define MAX_N 1000006 /// 主串长度 #define MAX_Tot 500005 /// 字典树上可能的最多的结点数 = Max串数 * Max串长 struct Aho{ struct state{ ]; int fail,cnt; }st[MAX_Tot]; /// 节点结构体 int Size; /// 节点个数 queue<int> que;/// BFS构建fail指针的队列 void init(){ while(que.size())que.pop();/// 清空队列 ;i<MAX_Tot;i++){/// 初始化节点,有时候 MLE 的时候,可以尝试将此初始化放到要操作的时候再来初始化 memset(st[i].next,,sizeof(st[i].next)); st[i].fail=st[i].cnt=; } Size=;/// 本来就有一个空的根节点 } void insert(char *S){/// 插入模式串 int len=strlen(S);/// 复杂度为O(n),所以别写进for循环 ;/// 当前结点是哪一个,从0即根开始 ;i<len;i++){ char c = S[i]; if(!st[now].next[c-'a']) st[now].next[c-'a']=Size++; now=st[now].next[c-'a']; } st[now].cnt++;/// 给这个串末尾打上标记 } void build(){/// 构建 fail 指针 st[].fail=-;/// 根节点的 fail 指向自己 que.push();/// 将根节点入队 while(que.size()){ int top = que.front(); que.pop(); ; i<; i++){ if(st[top].next[i]){/// 如果当前节点有 i 这个儿子 ) st[st[top].next[i]].fail=;/// 第二层节点 fail 应全指向根 else { int v = st[top].fail;/// 走向 top 节点父亲的 fail 指针指向的地方,尝试找一个最长前缀 ){/// 如果走到 -1 则说明回到根了 if(st[v].next[i]){/// 如果有一个最长前缀后面接着的也是 i 这个字符,则说明 top->next[i] 的 fail 指针可以指向这里 st[st[top].next[i]].fail = st[v].next[i]; break;/// break 保证找到的前缀是最长的 } v = st[v].fail;/// 否则继续往父亲的父亲的 fail 跳,即后缀在变短( KMP 思想 ) } ) st[st[top].next[i]].fail=;/// 如果从头到尾都没找到,那么就只能指向根了 } que.push(st[top].next[i]);/// 将这个节点入队,为了下面建立 fail 节点做准备 } } } } int get(int u){ ; while(u){ res = res + st[u].cnt; st[u].cnt = ; u = st[u].fail; } return res; } int match(char *S){ int len = strlen(S);/// 主串长度 ,now=;/// 主串能够和多少个模式串匹配的结果、当前的节点是哪一个 ; i<len; i++){ char c = S[i]; if(st[now].next[c-'a']) now=st[now].next[c-'a'];/// 如果匹配了,则不用跳到 fail 处,直接往下一个字符匹配 else { int p = st[now].fail; && st[p].next[c- ) p=st[p].fail;/// 跳到 fail 指针处去匹配 c-'a' ,直到跳到 -1 也就是没得跳的时候 ) now = ;/// 如果全部都不匹配,只能回到根节点了 else now = st[p].next[c-'a'];/// 否则当前节点就是到达了能够匹配的 fail 指针指向处 } if(st[now].cnt)/// 如果当前节点是个字符串的结尾,这个时候就能统计其对于答案贡献了,答案的贡献应该是它自己 + 它所有 fail 指针指向的节点 /// 实际也就是它匹配了,那么它的 fail 指向的前缀以及 fail 的 fail 实际上也应该是匹配了,所以循环跳 fail 直到无法再跳为止 res = res + get(now); } return res; } }ac; int T; int N; char S[MAX_N]; int main(){ // ac.init(); // ac.build(); // ac.match(); // ... ; }
带注释
#include <stdio.h> #include <stdlib.h> #include <string.h> struct Node { int cnt;//是否为该单词的最后一个结点 Node *fail;//失败指针 Node *next[];//Trie中每个结点的各个节点 }*queue[];//队列,方便用BFS构造失败指针 ];//主字符串 ];//需要查找的单词 Node *root;//头结点 void Init(Node *root)//每个结点的初始化 { root->cnt=; root->fail=NULL; ;i<;i++) root->next[i]=NULL; } void Build_trie(char *keyword)//构建Trie树 { Node *p,*q; int i,v; int len=strlen(keyword); ,p=root;i<len;i++) { v=keyword[i]-'a'; if(p->next[v]==NULL) { q=(struct Node *)malloc(sizeof(Node)); Init(q); p->next[v]=q;//结点链接 } p=p->next[v];//指针移动到下一个结点 } p->cnt++;//单词最后一个结点cnt++,代表一个单词 } void Build_AC_automation(Node *root) { ,tail=;//队列头、尾指针 queue[head++]=root;//先将root入队 while(head!=tail) { Node *p=NULL; Node *temp=queue[tail++];//弹出队头结点 ;i<;i++) { if(temp->next[i]!=NULL)//找到实际存在的字符结点 { //temp->next[i] 为该结点,temp为其父结点 if(temp==root)//若是第一层中的字符结点,则把该结点的失败指针指向root temp->next[i]->fail=root; else { //依次回溯该节点的父节点的失败指针直到某节点的next[i]与该节点相同, //则把该节点的失败指针指向该next[i]节点; //若回溯到 root 都没有找到,则该节点的失败指针指向 root p=temp->fail;//将该结点的父结点的失败指针给p while(p!=NULL) { if(p->next[i]!=NULL) { temp->next[i]->fail=p->next[i]; break; } p=p->fail; } //让该结点的失败指针也指向root if(p==NULL) temp->next[i]->fail=root; } queue[head++]=temp->next[i];//每处理一个结点,都让该结点的所有孩子依次入队 } } } } int query(Node *root) { //i为主串指针,p为模式串指针 ; Node *p=root; int len=strlen(s); ;i<len;i++) { v=s[i]-'a'; //由失败指针回溯查找,判断s[i]是否存在于Trie树中 while(p->next[v]==NULL && p!=root) p=p->fail; p=p->next[v];//找到后p指针指向该结点 if(p==NULL)//若指针返回为空,则没有找到与之匹配的字符 p=root; Node *temp=p;//匹配该结点后,沿其失败指针回溯,判断其它结点是否匹配 while(temp!=root)//匹配结束控制 { )//判断该结点是否被访问 { count+=temp->cnt;//由于cnt初始化为 0,所以只有cnt>0时才统计了单词的个数 temp->cnt=-;//标记已访问过 } else//结点已访问,退出循环 break; temp=temp->fail;//回溯 失败指针 继续寻找下一个满足条件的结点 } } return count; } int main() { int T,n; scanf("%d",&T); while(T--) { root=(struct Node *)malloc(sizeof(Node)); Init(root); scanf("%d",&n); ;i<n;i++) { scanf("\n%s",keyword); Build_trie(keyword); } Build_AC_automation(root); scanf("\n%s",s); printf("%d\n",query(root)); } ; }
指针版
; ; ; struct Aho{ struct StateTable{ int nxt[Letter]; int fail, cnt; bool vis; void init(){ memset(nxt, , sizeof(nxt)); fail = ; cnt = ; vis = false; } }Node[max_node]; int sz; queue<int> que; inline ].init(); sz = ; } inline void insert(char *s, int len){ ; ; i<len; i++){ int idx = s[i] - 'a'; if(!Node[now].nxt[idx]){ Node[sz].init(); Node[now].nxt[idx] = sz++; } now = Node[now].nxt[idx]; } Node[now].cnt++; } inline void build(){ Node[].fail = -; que.push(); while(!que.empty()){ int top = que.front(); que.pop(); ; i<Letter; i++){ if(Node[top].nxt[i]){ ) Node[ Node[top].nxt[i] ].fail = ; else{ int v = Node[top].fail; ){ if(Node[v].nxt[i]){ Node[ Node[top].nxt[i] ].fail = Node[v].nxt[i]; break; }v = Node[v].fail; }) Node[ Node[top].nxt[i] ].fail = ; }que.push(Node[top].nxt[i]); }?Node[ Node[top].fail ].nxt[i]:; } } } int Match(char *s){ , res = ; ; s[i]!='\0'; i++){ int idx = s[i] - 'a'; now = Node[now].nxt[idx]; int tmp = now; && !Node[tmp].vis){ res += Node[tmp].cnt; Node[tmp].vis = true; Node[tmp].cnt = ; tmp = Node[tmp].fail; } } return res; } }ac;
Trie 图
参考博客
http://blog.csdn.net/niushuai666/article/details/7002823
http://blog.csdn.net/silence401/article/details/52662605
http://blog.csdn.net/liu940204/article/details/51345954
http://blog.csdn.net/creatorx/article/details/71100840
相关题目
HDU 2222
题意 : 给出 n 个模式串再给出一个主串,问你有多少个模式串曾在这个主串上出现过
分析 : 模板题,注意每一次计数完成后要将 cnt 的值置为 0 以免重复计算
#include<bits/stdc++.h> using namespace std; #define MAX_N 1000006 /// 主串长度 #define MAX_Tot 500005 /// 字典树上可能的最多的结点数 = Max串数 * Max串长 struct Aho{ struct state{ ]; int fail,cnt; }st[MAX_Tot]; /// 节点结构体 int Size; /// 节点个数 queue<int> que;/// BFS构建fail指针的队列 void init(){ while(que.size())que.pop();/// 清空队列 ;i<MAX_Tot;i++){/// 初始化节点,有时候 MLE 的时候,可以尝试将此初始化放到要操作的时候再来初始化 memset(st[i].next,,sizeof(st[i].next)); st[i].fail=st[i].cnt=; } Size=;/// 本来就有一个空的根节点 } void insert(char *S){/// 插入模式串 int len=strlen(S);/// 复杂度为O(n),所以别写进for循环 ;/// 当前结点是哪一个,从0即根开始 ;i<len;i++){ char c = S[i]; if(!st[now].next[c-'a']) st[now].next[c-'a']=Size++; now=st[now].next[c-'a']; } st[now].cnt++;/// 给这个串末尾打上标记 } void build(){/// 构建 fail 指针 st[].fail=-;/// 根节点的 fail 指向自己 que.push();/// 将根节点入队 while(que.size()){ int top = que.front(); que.pop(); ; i<; i++){ if(st[top].next[i]){/// 如果当前节点有 i 这个儿子 ) st[st[top].next[i]].fail=;/// 第二层节点 fail 应全指向根 else { int v = st[top].fail;/// 走向 top 节点父亲的 fail 指针指向的地方,尝试找一个最长前缀 ){/// 如果走到 -1 则说明回到根了 if(st[v].next[i]){/// 如果有一个最长前缀后面接着的也是 i 这个字符,则说明 top->next[i] 的 fail 指针可以指向这里 st[st[top].next[i]].fail = st[v].next[i]; break;/// break 保证找到的前缀是最长的 } v = st[v].fail;/// 否则继续往父亲的父亲的 fail 跳,即后缀在变短( KMP 思想 ) } ) st[st[top].next[i]].fail=;/// 如果从头到尾都没找到,那么就只能指向根了 } que.push(st[top].next[i]);/// 将这个节点入队,为了下面建立 fail 节点做准备 } } } } int get(int u){ ; while(u){ res = res + st[u].cnt; st[u].cnt = ; u = st[u].fail; } return res; } int match(char *S){ int len = strlen(S);/// 主串长度 ,now=;/// 主串能够和多少个模式串匹配的结果、当前的节点是哪一个 ; i<len; i++){ char c = S[i]; if(st[now].next[c-'a']) now=st[now].next[c-'a'];/// 如果匹配了,则不用跳到 fail 处,直接往下一个字符匹配 else { int p = st[now].fail; && st[p].next[c- ) p=st[p].fail;/// 跳到 fail 指针处去匹配 c-'a' ,直到跳到 -1 也就是没得跳的时候 ) now = ;/// 如果全部都不匹配,只能回到根节点了 else now = st[p].next[c-'a'];/// 否则当前节点就是到达了能够匹配的 fail 指针指向处 } if(st[now].cnt)/// 如果当前节点是个字符串的结尾,这个时候就能统计其对于答案贡献了,答案的贡献应该是它自己 + 它所有 fail 指针指向的节点 /// 实际也就是它匹配了,那么它的 fail 指向的前缀以及 fail 的 fail 实际上也应该是匹配了,所以循环跳 fail 直到无法再跳为止 res = res + get(now); } return res; } }aho; int T; int N; char S[MAX_N]; int main(){ scanf("%d",&T); while(T--){ aho.init(); scanf("%d",&N); ;i<N;i++){ scanf("%s",S); aho.insert(S); } aho.build(); scanf("%s",S); printf("%d\n",aho.match(S)); } ; }
HDU 2896
题意 : 中文就不赘述了……
分析 : 模板题,可见的ascii码范围的话,直接开到128即可
#include<queue> #include<stdio.h> #include<string.h> using namespace std; ; ; ; struct Aho{ struct StateTable{ int Next[Letter]; int fail, id; }Node[Max_Tot]; int Size; queue<int> que; inline void init(){ while(!que.empty()) que.pop(); memset(Node[].Next, , ].Next)); Node[].fail = Node[].id = ; Size = ; } inline void insert(char *s, const int id){ int len = strlen(s); ; ; i<len; i++){ int idx = s[i]; if(!Node[now].Next[idx]){ memset(Node[Size].Next, , sizeof(Node[Size].Next)); Node[Size].fail = Node[Size].id = ; Node[now].Next[idx] = Size++; } now = Node[now].Next[idx]; } Node[now].id = id; } inline void BuildFail(){ Node[].fail = -; que.push(); while(!que.empty()){ int top = que.front(); que.pop(); ; i<Letter; i++){ if(Node[top].Next[i]){ ) Node[ Node[top].Next[i] ].fail = ; else{ int v = Node[top].fail; ){ if(Node[v].Next[i]){ Node[ Node[top].Next[i] ].fail = Node[v].Next[i]; break; }v = Node[v].fail; }) Node[ Node[top].Next[i] ].fail = ; }que.push(Node[top].Next[i]); } } } } inline void Get(int u, bool *used){ while(u){ if(!used[Node[u].id] && Node[u].id) used[Node[u].id] = true; u = Node[u].fail; } } bool Match(char *s, bool *used){ ; bool ok = false; ; s[i]; i++){ int idx = s[i]; if(Node[now].Next[idx]) now = Node[now].Next[idx]; else{ int p = Node[now].fail; && Node[p].Next[idx]==){ p = Node[p].fail; } ) now = ; else now = Node[p].Next[idx]; } if(Node[now].id) { Get(now, used); ok = true; } } if(ok) return true; return false; } }ac; char S[Max_Len]; ]; int main(void) { int n, m; memset(used, false, sizeof(used)); while(~scanf("%d", &n)){ ac.init(); ; i<=n; i++){ scanf("%s", S); ac.insert(S, i); } ac.BuildFail(); ; scanf("%d", &m); ; i<=m; i++){ scanf("%s", S); if(ac.Match(S, used)){ printf("web %d:", i); ; j<=n; j++){ if(used[j]){ printf(" %d", j); used[j] = false; } }puts(""); ans++; } } printf("total: %d\n", ans); } ; }
HDU 3065
题意 : 中文就不赘述了......
分析 : 还是模板题
#include<queue> #include<stdio.h> #include<string.h> using namespace std; ; ; ; struct Aho{ struct StateTable{ int Next[Letter]; int fail, id; }Node[Max_Tot]; int Size; queue<int> que; inline void init(){ while(!que.empty()) que.pop(); memset(Node[].Next, , ].Next)); Node[].fail = Node[].id = ; Size = ; } inline void insert(char *s, int id){ int len = strlen(s); ; ; i<len; i++){ int idx = s[i] - 'A'; if(!Node[now].Next[idx]){ memset(Node[Size].Next, , sizeof(Node[Size].Next)); Node[Size].fail = Node[Size].id = ; Node[now].Next[idx] = Size++; } now = Node[now].Next[idx]; } Node[now].id = id; } inline void BuildFail(){ Node[].fail = -; que.push(); while(!que.empty()){ int top = que.front(); que.pop(); ; i<Letter; i++){ if(Node[top].Next[i]){ ) Node[ Node[top].Next[i] ].fail = ; else{ int v = Node[top].fail; ){ if(Node[v].Next[i]){ Node[ Node[top].Next[i] ].fail = Node[v].Next[i]; break; }v = Node[v].fail; }) Node[ Node[top].Next[i] ].fail = ; }que.push(Node[top].Next[i]); } } } } inline void Get(int u, int *arr){ while(u){ if(Node[u].id) arr[Node[u].id]++; u = Node[u].fail; } } inline void Match(char *s, int *arr){ ; ; s[i]; i++){ ; continue; } int idx = s[i] - 'A'; if(Node[now].Next[idx]) now = Node[now].Next[idx]; else{ int p = Node[now].fail; && Node[p].Next[idx]==) p = Node[p].fail; ) now = ; else now = Node[p].Next[idx]; } if(Node[now].id) Get(now, arr); } } }ac; char S[Max_Len]; ]; ][]; int main(void) { memset(arr, , sizeof(arr)); int n; while(~scanf("%d", &n)){ ac.init(); ; i<=n; i++){ scanf("%s", str[i]); ac.insert(str[i], i); } ac.BuildFail(); scanf("%s", S); ac.Match(S, arr); ; i<=n; i++){ if(arr[i]){ printf("%s: %d\n", str[i], arr[i]); arr[i] = ; } } } ; }
AC自动机及其模板的更多相关文章
- AC自动机 (模板)
AC自动机是用来干什么的: AC自动机是用来解决多模匹配问题,例如有单词s1,s2,s3,s4,s5,s6,问:在文本串ss中有几个单词出现过,类似. AC自动机实现这个功能需要三个部分: 1.将所有 ...
- [hdu2222]ac自动机(模板)
题意:一个文本串+多个模板串的匹配问题 思路:裸的ac自动机. #pragma comment(linker, "/STACK:10240000,10240000") #inclu ...
- HDOJ-3065(AC自动机+每个模板串的出现次数)
病毒侵袭持续中 HDOJ-3065 第一个需要注意的是树节点的个数也就是tree的第一维需要的空间是多少:模板串的个数*最长模板串的长度 一开始我的答案总时WA,原因是我的方法一开始不是这样做的,我是 ...
- luogu AC自动机(模板)
完全忘了AC自动机怎么写了qwq,更别说AC自动机上DP了. 今天就好好地学习字符串好了qwq 提一下AC自动机的时间复杂度--设n是模式串的个数,m是文本串的长度,l是模式串的平均长度,那么它的时间 ...
- ac自动机俩模板
ac自动机算法正确性还没有理解,算法导论也看不懂..等懂了回来发算法专题. #include <cstdio> #include <cstring> using namespa ...
- AC自动机(模板) LUOGU P3808
传送门 解题思路 AC自动机,是解决多模匹配问题的算法,是字典树与kmp结合的算法,可以解决许多子串在文本串中出现的次数等信息.关键是实现一个fail指针,是指向更靠上的前缀相同字母,从而可以实现在文 ...
- AC自动机(模板+例题)
首先要明白AC自动机是干什么的: AC自动机其实就是一种多模匹配算法,那么你可能会问什么叫做多模匹配算法.下面是我对多模匹配的理解,与多模与之对于的是单模,单模就是给你一个单词,然后给你一个字符串,问 ...
- AC自动机(模板)
#include <cstdio> #include <cstring> #include <iostream> #include <cstdlib> ...
- hdu 2222 ac自动机更新模板 for onSite contest
http://acm.split.hdu.edu.cn/showproblem.php?pid=2222 #include <cstdio> #include <cstdlib> ...
随机推荐
- Cocos2d-X网络编程(4) Cocos2d中的网络通信协议——Socket通信
Socket,俗称网络套接字,本身并不是协议,而是一个调用接口,是对TCP/IP协议的封装和应用,.提供了一系列方法方便开发者进行网络通讯. TCP/IP协议是使用最早的通讯协议,它是传输层协议,主要 ...
- 关于mysql中修改某个字段类型,以及备份表中数据到新建的表中,从新建的表中移除数据到修改过的表中
1:修改表中某个字段的类型 alter table usertable MODIFY dddd VARCHAR(50); 其中MODIFY是指修改表中字段的属性 alter表示修改表的意思 2:备份表 ...
- finereport 带多参数查询
1.sql语句 ${,""," and dt.货主地区='"+comboBox0+"'")} ${,""," ...
- XSS注入常用语句(整理)
<script>alert('hello,gaga!');</script> //经典语句,哈哈! >"'><img src="javas ...
- 配置NAT
NAT是将IP数据报文报头中的IP地址转换为另-一个IP地址的过程,主要用于实现内部网络(私有IP地址)访问外部网络(公有IP地址)的功能.NAT有3种类型:静态NAT.动态地址NAT以及网络地址端口 ...
- (4.31)sql server中的xml数据操作
关键词:xml数据转为行列方式显示 常规案例: declare @data xml declare @h int set @data=' <bookstore> <row> & ...
- 剑指Offer编程题(Java实现)——反转链表
题目描述 输入一个链表,反转链表后,输出新链表的表头. 思路一 使用头插法迭代进行反转 实现 /* public class ListNode { int val; ListNode next = n ...
- Tomcat 一台机器运行多个Tomcat
转 https://www.cnblogs.com/andy1234/p/8866588.html 在一台Win10 PC 上面同时开启两个Tomcat系统为例. 1. 硬件环境 2. 到Tomcat ...
- CDQ分治总结
\(CDQ\)分治小结 标签:知识点总结 阅读体验:https://zybuluo.com/Junlier/note/1326395 身为一个资深菜鸡 \(CDQ\)分治一开始学了一晚上,后来某一天又 ...
- tcp和udp详解??
TCP:面向连接的可靠传输 tcp规定了:传输服务必须建立连接 传输结束必须断开连接 传输数据必须保证可靠 数据的可靠性:无重复.无丢失.无失序.无差错. 建立连接(三次握手): ...