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> ...
随机推荐
- SPA应用性能优化(懒加载)
前提: 如今开发方式都是采用前后台分离的方式,前台采用的方式则是单页面应用开发简称SPA,这种开发模式最大的一个特点就是将有所代码打包成了一个文件, 这会导致了一个问题就是如果这个应用过大,打出来的这 ...
- 【嵌入式 Linux文件系统】如何使用NFS文件系统
(1)内核配置 取消选项 General setup-->Initial RAM filesystem and RAM disk (initramfs/initrd) support 进入Fil ...
- 如何将数据库导入到本地MySQL
有两个方法:(1)在MySQL的客户端进行导入,比如: http://jingyan.baidu.com/article/6dad507517c11aa123e36ea0.html (2)方法:常用s ...
- 如何使用js在移动端和PC端居中
在手机移动端和PC端控制居中是一个很蛋痛的问题,因为屏幕宽度在变化,所以就不要写死样式,那么我想用JS来控制,灵活的控制宽度,需要注意这三个时候: (1)首先需要在页面刚加载的时候就调用此函数, (2 ...
- 能够打开国内网络,比如百度微信,但是打不开外国网站,该怎么解决(主要是DNS的问题)
(1)公司设置局域网外网打不开解决方法一: 如果是代理服务器上网,是因为服务上没有映射好外网访问网页的!解决方法是在服务器上开一个端口映射软件! 如果是路由器上网,就是路由器上没有映射外网访问的端口, ...
- Distributed Deep Learning
安利一下刘铁岩老师的<分布式机器学习>这本书 以及一个大神的blog: https://zhuanlan.zhihu.com/p/29032307 https://zhuanlan.zhi ...
- 前端开发HTML&css入门——HTML
HTML究竟为何物?其实HTML就是一种标记语言,英文全称为Hypertext Markup Language,翻译过来就叫超文本标记语言.它的作用就是负责负责网页的三个要素之中的结构. HTML使用 ...
- Api接口管理工具推荐
在App开发过程中少不了跟服务端打交道,各种HTTP接口调试.返回数据处理占据了不少开发时间,一款好的接口管理工具就非常有必要了.接口管理工具一方面起到链接后台开发人员和App开发人员的作用,另一方面 ...
- 2019-11-26-Resharper-去掉注释拼写
title author date CreateTime categories Resharper 去掉注释拼写 lindexi 2019-11-26 8:42:5 +0800 2018-09-04 ...
- Java并发(基础知识)——显示锁和同步工具类
显示锁 Lock接口是Java ...