AC自动机

AC自动机

AC自动机有一个很出色的功能:实现多模式匹配。

多模式匹配:模式串有多个,主串只有一个,要进行多次模式串匹配。如果用KMP就要一个一个模式串进行匹配,效率低。AC自动机就可以做到,只要经过一些预处理之后,扫描一遍主串,就可以找出所有模式串。

fail指针含义:若fail[i]=j,则word[j]word[i]的最长后缀。
fail指针的目的和意义:通过fail指针,把以s[i]为结尾的所有后缀的个数加起来,从而把匹配的模式串加起来。
如何构建fail指针:bfs层次遍历构建。
这个AC自动机感觉也可以 查后缀(想想fail指针的含义)

在这里插入图片描述

数据结构:

int n; //模式串个数
string s; //模式串
string text; //文本串
int trie[1000006][30],cnt[1000006],idx,fail[1000006]; //重要数据结构

先将输入的模式串构建成一棵字典树

void insert(string s)
{int p=0;for(int i=0;s[i];++i){int c=s[i]-'a';if(!trie[p][c]) trie[p][c]=++idx;p=trie[p][c];}cnt[p]++;
}

再设置各个节点的fail指针:bfs(层次遍历)

void getfail()
{queue<int> q;for(int i=0;i<26;++i){if(trie[0][i])q.push(trie[0][i]);fail[trie[0][i]]=0;}while(q.size()){int now=q.front();q.pop();for(int i=0;i<26;++i){if(trie[now][i]){fail[trie[now][i]]=trie[fail[now]][i];q.push(trie[now][i]);}elsetrie[now][i]=trie[fail[now]][i];}}
}

遍历文本串,查询出有多少个匹配的模式串:

int query(string s)
{int now=0,ans=0;for(int i=0;s[i];++i){now=trie[now][s[i]-'a'];for(int j=now;j&&cnt[j]!=-1;j=fail[j]){ans+=cnt[j];cnt[j]=-1;}}return ans;
}

分享B站学习链接:

1.[算法]轻松掌握ac自动机_哔哩哔哩_bilibili

刷题练手链接:

P3808 【模板】AC 自动机(简单版) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

参考代码:

#include <bits/stdc++.h>
using namespace std;int n;
string s;
string text;
int trie[1000006][30],cnt[1000006],idx,fail[1000006];
//vector<int> v[N]; //可以存编号为i的结点存放了字符串长度为多少的串 void insert(string s)
{int p=0;for(int i=0;s[i];++i){int c=s[i]-'a';if(!trie[p][c]) trie[p][c]=++idx;p=trie[p][c];}cnt[p]++;
}void getfail()
{queue<int> q;for(int i=0;i<26;++i){if(trie[0][i])q.push(trie[0][i]);fail[trie[0][i]]=0;}while(q.size()){int now=q.front();q.pop();for(int i=0;i<26;++i){if(trie[now][i]){fail[trie[now][i]]=trie[fail[now]][i];q.push(trie[now][i]);}elsetrie[now][i]=trie[fail[now]][i]; //在构建fail指针时,trie在改变,它记录着 在上层 最近的 哪里 会有i这个元素 }}
}int query(string s)
{int now=0,ans=0;for(int i=0;s[i];++i){
//		cout<<i<<' '<<s[i]<<' '<<now<<' '<<trie[now][s[i]-'a']<<endl;now=trie[now][s[i]-'a'];for(int j=now;j&&cnt[j]!=-1;j=fail[j]){
//			cout<<j<<' '<<fail[j]<<endl; ans+=cnt[j];cnt[j]=-1; //这个模板有个问题:只能做一次询问。}}return ans;
}int main()
{cin >> n;while(n--){cin >> s;insert(s);}cin >> text;getfail();cout << query(text) << endl;return 0;
}

模板注意点:
1.插入的字符可以是什么?一般是’a’-'z’共26种.但也有题目会说是 可见字符,可见字符 应该算95个,从32-126
2.如果有多个主串,要注意加一个bool flag[N]处理一下。

裸题:HDU-2222 Keywords Search

2017 ICPC 青岛网络赛 C-The Dominator of Strings
题意:多组样例,每次给出N个字符串,求出N个串的母串。
代码:一般参考以下模板!

#include <bits/stdc++.h>
using namespace std;#define fi first
#define se second
int T,n,len;
string s,text;
int trie[100006][26],idx,fail[100006],cnt[100006];void Init() {for(int i=0;i<=idx;i++){cnt[i]=0;
//        fail[i]=0;  for(int j=0;j<26;j++)trie[i][j]=0;}idx=0;
}inline void insert(string s){int p=0;for(int i=0;s[i];++i){int c=s[i]-'a';if(!trie[p][c]) trie[p][c]=++idx;p=trie[p][c];}cnt[p]++;
}/*
感觉以下做法:"有一个位置匹配了,就往后配" 这种做法 比较暴力
但有些题 竟然 跑得更快   很奇怪很奇怪很奇怪。。。 
*/ 
int query(string s){int ans=0;for(int i=0;s[i];++i){int c=s[i]-'a';int u=0;int j=0;while(trie[u][c]){if(cnt[trie[u][c]]){ans+=cnt[trie[u][c]];cnt[trie[u][c]]=0;}u=trie[u][c];j++;if(i+j>=s.size()) break; //注意加这一行!不然有些时候会寄! c=s[i+j]-'a';}}return ans;
}/*
正常的AC自动机 模板 
*/
//void getfail(){
//	queue<int> q;
//	for(int i=0;i<26;++i) {
//		if(trie[0][i]) q.push(trie[0][i]);
//		fail[trie[0][i]]=0;
//	}
//	while(q.size()){
//		int now=q.front();
//		q.pop();
//		for(int i=0;i<26;++i){
//			if(trie[now][i]){
//				fail[trie[now][i]]=trie[fail[now]][i];
//				q.push(trie[now][i]);
//			}else{
//				trie[now][i]=trie[fail[now]][i];
//			}
//		}
//	}
//}
//
//int query(string &s){
//	int now=0,ans=0;
//	for(int i=0;s[i];++i){
//		now=trie[now][s[i]-'a'];
//		for(int j=now;j&&cnt[j]!=-1;j=fail[j]){
//			ans+=cnt[j];
//			cnt[j]=-1;
//		}
//	}
//	return ans;
//}int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>T;while(T--){cin>>n;Init(); //学习! len=0;for(int i=1;i<=n;++i) {cin>>s;insert(s);if(s.size()>len) text=s,len=s.size();}
//		getfail();if(query(text)==n) cout<<text<<'\n';else cout<<"No\n";}
}

例题:HDU - 2896 病毒侵袭
参考代码:
这道题 字符是可见字符,有多个主串

#include <bits/stdc++.h>
using namespace std;int n,m;
string s,text;
vector<int> v;
int tot;const int N=1e6+5;
int trie[N][100],cnt[N],idx,fail[N];
bool flag[N]; //标记 void insert(string s,int id){int p=0;for(int i=0;s[i];++i){int c=s[i]-32;if(!trie[p][c]) trie[p][c]=++idx;p=trie[p][c];}cnt[p]=id;
}void getfail(){queue<int> q;for(int i=0;i<95;++i){if(trie[0][i])q.push(trie[0][i]);fail[trie[0][i]]=0;}while(q.size()) {int now=q.front();q.pop();for(int i=0;i<95;++i){if(trie[now][i]){fail[trie[now][i]]=trie[fail[now]][i];q.push(trie[now][i]);}else trie[now][i]=trie[fail[now]][i];}}
}void query(string s){int now=0;for(int i=0;s[i];++i){now=trie[now][s[i]-32];for(int j=now;j&&cnt[j]!=-1;j=fail[j]){if(cnt[j] && flag[j]) v.push_back(cnt[j]);flag[j]=0; //这个有个问题:只能查询一次 }}
}int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n;for(int i=1;i<=n;++i){cin>>s;insert(s,i);}getfail();cin>>m;for(int i=1;i<=m;++i){cin>>text;v.clear();for(int i=0;i<=idx;++i) if(cnt[i]) flag[i]=1; //为了实现多次扫描主串 query(text);if(v.size()){sort(v.begin(),v.end());tot++;cout<<"web "<<i<<":";for(auto x:v) cout<<' '<<x;cout<<'\n';}}cout<<"total: "<<tot<<'\n';
}

HDU - 3065 病毒侵袭持续中
这道题 主串有多个子串会跟模式串匹配要算多次
怎么办?答:去掉cnt[j]=-1 以及 条件的 cnt[i]!=-1
这样,主串出现多个模式串,就能算多次了 。
参考代码:

#include <bits/stdc++.h>
using namespace std;int n;#define pii pair<int,int>
#define fi first
#define se second
#define ms(a,x) memset(a,x,sizeof a)
//数据结构
const int N=5e4+10; //应该是模式串总长 
string s[1003],text;
int trie[N][100],cnt[N],idx,fail[N];
vector<pii> v;
unordered_map<int,int> mp; void insert(string s,int k){int p=0;for(int i=0;s[i];++i){int c=s[i]-'A';if(!trie[p][c]) trie[p][c]=++idx;p=trie[p][c]; }cnt[p]=k;
}void getfail(){queue<int> q;for(int i=0;i<26;++i){if(trie[0][i])q.push(trie[0][i]);fail[trie[0][i]]=0;}while(q.size()){int now=q.front();q.pop();for(int i=0;i<26;++i){if(trie[now][i]){fail[trie[now][i]]=trie[fail[now]][i];q.push(trie[now][i]);}else trie[now][i]=trie[fail[now]][i];}}
}void query(string s){int now=0;for(int i=0;s[i];++i){if(s[i]>'Z' || s[i]<'A') {now=0;continue;}now=trie[now][s[i]-'A'];for(int j=now;j;j=fail[j]){if(cnt[j]) mp[cnt[j]]++;}}
}bool cmp(pii x,pii y){return x.fi<y.fi;
}int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);while(cin>>n){ms(trie,0);ms(cnt,0);ms(fail,0);idx=0;v.clear();mp.clear();for(int i=1;i<=n;++i){cin>>s[i];insert(s[i],i);}getfail();cin>>text;query(text);for(auto x:mp) v.push_back(x); sort(v.begin(),v.end(),cmp);for(auto x:v){cout<<s[x.fi]<<": "<<x.se<<'\n';}}
} 
由若干模式串构建串

HDU - 2825 Wireless Password
题意:给你M个模式串,问有多少种方案 构造长度为N的 至少包含K个模式串的 串。
注意:
1.字符串会包含,如有模式串she he,串she包含由模式串she和he
有一点 串包含 的意思在里面,所以可以想到用AC自动机把模式串全部扔进去。
2.然后用状压dp进行转移

参考

代码:

#include <bits/stdc++.h>
using namespace std;const int N=110;
const int MOD=20090717;
int trie[N][26],fail[N],cnt[N],idx;
int n,m,k;
string s;
int dp[30][110][1<<10]; //dp[i][j][k]:走到第i步 到达j结点 状态为k 的方案数 
int num[1<<10]; //num[i]:状态为i的1的数目 void insert(string s,int id){int p=0;for(int i=0;s[i];++i){int c=s[i]-'a';if(!trie[p][c]) trie[p][c]=++idx;p=trie[p][c];}cnt[p]=1<<id;
}void getfail(){queue<int> q;for(int i=0;i<26;++i){if(trie[0][i])q.push(trie[0][i]);fail[trie[0][i]]=0;}while(q.size()){int now=q.front();q.pop();cnt[now]|=cnt[fail[now]]; //因为提到 字符串 后缀包含 for(int i=0;i<26;++i){if(trie[now][i]){fail[trie[now][i]]=trie[fail[now]][i];q.push(trie[now][i]);}elsetrie[now][i]=trie[fail[now]][i];}}
}void getsum(){for(int i=0;i<(1<<10);++i){num[i]=0;for(int j=0;j<10;++j)if(i&(1<<j)) num[i]++;}
} int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);getsum(); //预处理 while(cin>>n>>m>>k && (n||m||k)){memset(trie,0,sizeof trie);memset(fail,0,sizeof fail);memset(cnt,0,sizeof cnt);idx=0; memset(dp,0,sizeof dp);for(int i=1;i<=m;++i) {cin>>s;insert(s,i-1);}getfail();dp[0][0][0]=1; for(int i=0;i<n;++i){ //遍历字符 for(int j=0;j<=idx;++j){ //遍历结点编号 for(int kk=0;kk<(1<<m);++kk){ //遍历状态 if(dp[i][j][kk]){for(int id=0;id<26;++id){ //遍历j号点下面的各个分支点 int u=trie[j][id];int state=kk|cnt[u];dp[i+1][u][state]+=dp[i][j][kk];dp[i+1][u][state]%=MOD;}} }}} int ans=0;for(int i=0;i<(1<<m);++i){
//			cout<<num[i]<<endl;if(num[i]>=k){for(int j=0;j<=idx;++j){ans=(ans+dp[n][j][i])%MOD;}}}cout<<ans<<endl;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/6393.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Codeforces Round 943 (Div. 3) G1. Division + LCP (easy version) 二分+KMP

Division LCP (easy version) 题目描述 This is the easy version of the problem. In this version l r lr lr . You are given a string s s s . For a fixed k k k , consider a division of s s s into exactly k k k continuous substrings w 1 , … , w k w_1…

【全网首出】npm run serve报错 Expression: thread_id_key != 0x7777

总结 困扰了一天&#xff01;&#xff01;&#xff01;一直以为是自己哪里配置错了&#xff0c; 结果最后发现是node.js官方的问题&#xff0c; Node.js v16.x版本的fibers.node被弃用 本文阅读大概&#xff1a;3min #npm run serve时就报错 #找了一天的文章&#xff0c;找不…

# 从浅入深 学习 SpringCloud 微服务架构(八)Sentinel(1)

从浅入深 学习 SpringCloud 微服务架构&#xff08;八&#xff09;Sentinel&#xff08;1&#xff09; 一、sentinel&#xff1a;概述 1、前言 – 服务熔断 Hystrix 的替换方案。 1&#xff09;2018年底 Netflix 官方宣布 Hystrix 已经足够稳定&#xff0c;不再积极开发 Hys…

JVM笔记2--垃圾收集算法

1、如何确认哪些对象“已死” 在上一篇文章中介绍到Java内存运行时的各个区域。其中程序计数器、虚拟机栈、本地方法栈3个区域随着线程而生&#xff0c;随线程而灭&#xff0c;栈中的栈帧随着方法的进入和退出而有条不紊的执行着入栈和出栈操作。每个栈帧中分配多少内存基本上…

组队竞赛和删除公共字符

这里附上两个题目的链接 题目一&#xff1a;删除公共字符_牛客题霸_牛客网 (nowcoder.com) 题目二&#xff1a;组队竞赛_牛客笔试题_牛客网 (nowcoder.com) 第一题 分析&#xff1a; 当我们看见这个题目的时候&#xff0c;可以使用传统的暴力查找方式&#xff0c;如判断第一个…

VsCode | 修改首页启动页 Logo

VsCode | 修改首页启动页 Logo 最终效果&#xff1a; 插件的安装 先安装插件 Custom CSS and JS Loader 插件配置 Ctrl Shift P 输入 打开用户设置&#xff0c;在末尾添加 "vscode_custom_css.imports": [""]下载 Logo 下载 Logo 点我下载 引入…

json转excel

前面有介绍过excel文件转换成json文件的方法&#xff0c;那json文件转excel文件呢&#xff1f;如果json文件里数据格式都是统一的话&#xff0c;那么也比较容易就转。 我们假设json文件中是一个json数组&#xff0c;每条json数据的属性字段都一样&#xff0c;手写一段node.js例…

若依分离版-前端使用echarts组件

1 npm list:显示已安装的模块 该命令用于列出当前项目的所有依赖关系&#xff0c;包括直接依赖和间接依赖。执行 npm list 时&#xff0c;npm 将从当前目录开始&#xff0c;递归地列出所有已安装的模块及其版本信息 npm list 2 npm outdated:用于检查当前项目中的npm包是否有…

亚马逊云科技AWS免费证书-EC2服务器设计(含题库)

亚马逊云AWS官方程序员专属免费证书又来了&#xff01;这次证书是关于AWS EC2实例的设计和搭建&#xff0c;EC2作为AWS服务的核心&#xff0c;是学好AWS的第一步。强推没有任何AWS背景和转码的小伙伴去学&#xff01;学完也能变成AWS开发大神&#xff01; 证书名字叫Getting St…

使用 TensorFlow 和 Keras 构建 U-Net

原文地址&#xff1a;building-a-u-net-with-tensorflow-and-keras 2024 年 4 月 11 日 计算机视觉有几个子学科&#xff0c;图像分割就是其中之一。如果您要分割图像&#xff0c;则需要在像素级别决定图像中可见的内容&#xff08;执行分类时&#xff09;&#xff0c;或者从像…

第Ⅰ章-IV npm yarn pnpm 包管理器

第Ⅰ章-Ⅰ 了解Vue3 创建一个Vue3项目 第Ⅰ章-Ⅱ Vue3自定义创建项目 项目文件详解 第Ⅰ章-III Vite 创建vue3 项目 第Ⅰ章-IV npm yarn pnpm 包管理器 第Ⅰ章-IV npm yarn pnpm 包管理器 简介npm工作原理 yarn工作原理 pnpm工作原理 功能脚本添加依赖移除依赖安装所有依赖查看…

Oracle23ai来了,23爱,全能、超级巨兽...

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、My…

[图解]关于SysML v2(1)大刀阔斧 对比 伪创新圈子

1 00:00:03,960 --> 00:00:08,270 OMG在2月份&#xff0c;这里写了4月 2 00:00:08,440 --> 00:00:13,530 应该是2月&#xff0c;发布了 3 00:00:13,870 --> 00:00:17,700 SysML v2的 beta 2版本 4 00:00:17,870 --> 00:00:19,780 也是当前最新的版本 5 00:00:2…

关于灰度发布

目录 一 来源 二 运行过程 三 适用范围 一 来源 灰度发布&#xff0c;也叫金丝雀发布&#xff0c;起源是&#xff0c;矿井工人发现&#xff0c;金丝雀对瓦斯气体很敏感&#xff0c;矿工会在下井之前&#xff0c;先放一只金丝雀到井中&#xff0c;如果金丝雀不叫了&#xff…

【DevOps】掌控云端:Google Cloud SDK 快速上手

一、Google Cloud SDK Google Cloud SDK (Software Development Kit) 是一组工具,包括 gcloud、gsutil 和 bq,用于通过命令行或自动化脚本访问和管理 Google Cloud 资源和服务。以下是 Cloud SDK 的详细介绍: 1、gcloud 命令行工具 gcloud 是 Cloud SDK 的核心组件,用于管理…

ES的脑裂现象

目录 0 集群结点的职责1 什么是脑裂现象2 造成脑裂现象的原因2.1 网络问题&#xff08;最常见&#xff09;2.2 主节点负载过大&#xff0c;资源耗尽&#xff0c;别的结点ping不到主节点2.3 主节点JVM内存回收时间过长导致 3 脑裂现象的解决方案3.1 局域网部署3.2 角色分离&…

主成分分析(PCA)学习

概述 主成分分析&#xff08;Principal Component Analysis&#xff0c;PCA&#xff09;是一种常用的数据降维方法&#xff0c;它通过线性变换将原始数据变换为一组各维度线性无关的表示&#xff0c;通常用于提取数据的主要特征分量。PCA 的目标是从原始数据中提取出最重要的特…

python实验一 简单的递归应用

实验一 实验题目 1、兔子繁殖问题(Fibonacci’s Rabbits)。一对兔子从出生后第三个月开始&#xff0c;每月生一对小兔子。小兔子到第三个月又开始生下一代小兔子。假若兔子只生不死&#xff0c;一月份抱来一对刚出生的小兔子&#xff0c;问一年中每个月各有多少只兔子。 &…

[每日AI·0501]GitHub 版 Devin,Transformer的强力挑战者 Mamba,Sora 制作细节与踩坑,OpenAI 记忆功能

AI 资讯 国资委&#xff1a;加快人工智能等新技术与制造全过程、全要素深度融合GitHub版 Devin 上线&#xff0c;会打字就能开发应用&#xff0c;微软 CEO&#xff1a;重新定义 IDE在12个视频理解任务中&#xff0c;Mamba 先打败了 TransformerSora 会颠覆电影制作吗&#xff…

(delphi11最新学习资料) Object Pascal 学习笔记---第11章 ( 接口)

第11章 接口 ​ 与C及其他语言不同&#xff0c;Object Pascal不支持多重继承&#xff0c;这意味着每个类只能有一个单一的基类。 ​ 多重继承的实用性是面向对象编程专家争论的议题之一。Object Pascal中缺少多重继承可以被看做一种劣势&#xff0c;因为您没有C的功能强大&am…