【模版 Luogu P3808/P3796/P5357】AC自动机(简论)
浙江集训Day9,没有出任何实质性成果,只好把昨天打完的板子记一下。
该博客基于luogu的三道模版题。只有一个大致的讲解,主要提供代码给自己参考。
-----------------------------------------------------------------------
(7.14)
一、AC自动机
AC自动机,一个有着令人容易误会的名字的有限状态自动机结构,主要被应用在多模式串的文本匹配问题中。理解AC自动机,首先要熟悉KMP算法和字典树。使用KMP可以分开对每个模式串进行计数,但是对目标串的扫描次数会爆炸。实际上,KMP算法本身也可以从有限状态自动机的角度来理解(简单理解大概就是跳转能够达到的状态是有限个)。AC自动机与KMP都含有类似的fail指针结构。通俗理解fail的意义,我们先建立原串的trie树(KMP则就是原串),然后预处理出每个节点“在当前点a匹配不下去了,我要找一个字典树的前缀串是该串的后缀串”代表的b(这样的意义是,匹配到a就一定匹配到了b)。这就是构建自动机的过程。平凡的ACAM在匹配时,我们沿着字典树往下走,下一个字符失配就沿着fail边去跳转它的后缀代表的匹配状态,直到找到一个可以匹配文本串的下个字符的后缀状态为止。同时,每找到一个串,我们就要沿着fail边翻出它的后缀串,因为这些后缀也都被匹配到了。这就导致AC自动机的匹配复杂度有了可优化的空间。
所谓的trie图优化,就是在建立AC自动机时直接把失配的那个字符对应的边连到目标后缀上,这样可以省去每次失配跳fail边的麻烦。同时,一个串有很多后缀,但是并没有都出现在模式串中,中间空状态的跳转没有意义;那么我们就新开一个数组记录下它的第一个是结束节点的fail目标状态(即这个后缀存在于模式串中)的位置,然后每次沿着这个边跳转即可。
代码明天放,顺便安利让我学会AC自动机的dalao的两篇博客,受益匪浅。
https://www.cnblogs.com/sclbgw7/p/9260756.html (AC自动机的构建)
https://www.cnblogs.com/sclbgw7/p/9875671.html (AC自动机的两种优化)
---------------------------------------------------
(7.15)
二、代码
模版一:统计出现模式串的个数
- #include <iostream>
- #include <cstdio>
- #include <cstring>
- #include <queue>
- #define BUG puts("findone")
- #define maxn 1000000 + 10
- template <typename T>
- void read(T &x) {
- x = 0;
- int f = 1;
- char ch = getchar();
- while (!isdigit(ch)) {
- if (ch == '-')
- f = -1;
- ch = getchar();
- }
- while (isdigit(ch)) {
- x = x * 10 + (ch ^ 48);
- ch = getchar();
- }
- x *= f;
- return;
- }
- using namespace std;
- char s[maxn];
- namespace ACAM {
- int trie[26][maxn], pi[maxn], cnt[maxn], last[maxn];
- const int root(1);
- int tot = 1;
- void Insert(char *s) {
- int nd = root, len = strlen(s);
- for (int i = 0; i < len; ++i) {
- int c = s[i] - 'a';
- if (!trie[c][nd])
- trie[c][nd] = ++tot;
- nd = trie[c][nd];
- }
- ++cnt[nd];
- }
- void Build_ACAM() {
- for (int i = 0; i < 26; ++i)
- trie[i][0] = root;
- pi[root] = 0;
- queue<int> que;
- que.push(root);
- while (!que.empty()) {
- int nd = que.front();
- que.pop();
- for (int c = 0; c < 26; ++c) {
- if (!trie[c][nd]) {
- // trie[c][nd] = trie[c][pi[nd]]; //这句话就是trie图优化,这题不用它反而跑得更快……
- continue;
- }
- int son = trie[c][nd], nxt = pi[nd];
- while (nxt && !trie[c][nxt])
- nxt = pi[nxt];
- pi[son] = trie[c][nxt];
- last[son] = cnt[pi[son]] ? pi[son] : last[pi[son]]; //last优化,它在三道题中都很优秀
- que.push(son);
- }
- }
- }
- int Match(char *s) {
- int len = strlen(s), nd = root, ans = 0;
- for (int i = 0; i < len; ++i) {
- int c = s[i] - 'a';
- while (nd && !trie[c][nd]) { //如果加了trie图优化就不用每次跳fail边来找后缀,因为trie图优化直接记录可匹配的下一个后缀
- nd = pi[nd];
- }
- nd = trie[c][nd];
- for (int t = nd; t && ~cnt[t]; t = last[t])
- ans += cnt[t], cnt[t] = -1;
- }
- return ans;
- }
- } using namespace ACAM;
- int main() {
- // freopen("testdata.txt", "r", stdin);
- // freopen("ans.txt", "w", stdout);
- int n;
- scanf("%d", &n);
- for (int i = 1; i <= n; ++i) {
- scanf("%s", s);
- Insert(s);
- }
- Build_ACAM();
- scanf("%s", s);
- cout << Match(s);
- return 0;
- }
模版二:AC自动机(加强版):多组数据,输出出现最多的串的出现次数,按输入顺序输出这些串。
- #include <iostream>
- #include <cstdio>
- #include <cstring>
- #include <queue>
- #include <vector>
- #define BUG puts("findone")
- #define maxn 70 * 150 + 10
- template <typename T>
- void read(T &x) {
- x = 0;
- int f = 1;
- char ch = getchar();
- while (!isdigit(ch)) {
- if (ch == '-')
- f = -1;
- ch = getchar();
- }
- while (isdigit(ch)) {
- x = x * 10 + (ch ^ 48);
- ch = getchar();
- }
- x *= f;
- return;
- }
- using namespace std;
- char s[1000010], T[151][80];
- namespace ACAM {
- int trie[26][maxn], pi[maxn], cnt[maxn], last[maxn], sum[maxn], id[maxn];
- const int root(1);
- int tot = 1;
- void Insert(char *s, int pos) {
- int nd = root, len = strlen(s);
- for (int i = 0; i < len; ++i) {
- int c = s[i] - 'a';
- if (!trie[c][nd])
- trie[c][nd] = ++tot;
- nd = trie[c][nd];
- }
- ++cnt[nd], id[nd] = pos; //id数组的意义是记录每个节点(状态)在原输入顺序中所对应的串
- }
- void Build_ACAM() {
- for (int i = 0; i < 26; ++i)
- trie[i][0] = root;
- pi[root] = 0;
- queue<int> que;
- que.push(root);
- while (!que.empty()) {
- int nd = que.front();
- que.pop();
- for (int c = 0; c < 26; ++c) {
- if (!trie[c][nd]) {
- trie[c][nd] = trie[c][pi[nd]];
- continue;
- }//优化位置
- int son = trie[c][nd], nxt = pi[nd];
- while (nxt && !trie[c][nxt])
- nxt = pi[nxt];
- pi[son] = trie[c][nxt];
- last[son] = cnt[pi[son]] ? pi[son] : last[pi[son]];
- que.push(son);
- }
- }
- }
- void Match(char *s) {
- int len = strlen(s), nd = root;
- for (int i = 0; i < len; ++i) {
- int c = s[i] - 'a';
- // while (nd && !trie[c][nd])
- // nd = pi[nd];
- nd = trie[c][nd];
- for (int t = nd; t; t = last[t])
- if (cnt[t])
- ++sum[t];
- }
- vector<int> ans;
- for (int i = 1; i <= tot; ++i)
- if (ans.empty() || sum[i] == sum[ans.front()])
- ans.push_back(i);
- else if (sum[i] > sum[ans.front()]) {
- ans.clear();
- ans.push_back(i);
- }
- printf("%d\n", sum[ans.front()]);
- for (int i = 0; i < ans.size(); ++i)
- puts(T[id[ans[i]]]);
- }
- } using namespace ACAM;
- int main() {
- // freopen("testdata.txt", "r", stdin);
- // freopen("ans.txt", "w", stdout);
- ios::sync_with_stdio(0); //某种加快iostream的黑科技 据称读入字符串飞快
- cin.tie(0);
- while (19260817) {
- int n;
- cin >> n;
- if(n == 0) break;
- tot = 1;
- memset(trie, 0, sizeof(trie));
- memset(sum, 0, sizeof(sum));
- memset(cnt, 0, sizeof(cnt));
- memset(pi, 0, sizeof(pi));
- memset(last, 0, sizeof(last));
- memset(id, 0, sizeof(id));
- for (int i = 1; i <= n; ++i) {
- cin >> T[i];
- Insert(T[i], i);
- }
- Build_ACAM();
- cin >> s;
- Match(s);
- }
- return 0;
- }
模版三、AC自动机(二次加强版):统计每个模式串出现的次数。一开始的策略是每到一个位置就暴力跳last边来找后缀,但是时间只有1000ms,T掉了几个点。参看题解给出的解法是:统计每个状态的出现次数,然后从fail[u]向u连边,构成一棵树。这棵树被称作fail树,满足每个节点的祖先都是它的后缀。这样,每个模式串的出现次数就是它自己的出现次数+以它为后缀的串的出现次数,也就是以它为根的子树的大小。trie树上某状态的祖先则是它的前缀。fail树的性质很好,也具有广泛的应用。
- #include <iostream>
- #include <cstdio>
- #include <cstring>
- #include <queue>
- #define maxs 2000010
- #define maxn 200010
- using namespace std;
- char T[maxn], s[maxs];
- int head[maxn], top;
- struct E {
- int to, nxt;
- } edge[maxn];
- void Insert_edge(int u, int v) {
- edge[++top] = (E) {v, head[u]};
- head[u] = top;
- }
- namespace ACAM {
- int trie[26][maxn], tot = 1, cnt[maxn], pi[maxn], last[maxn], end[maxn];
- int id[maxn];
- const int root(1);
- void Insert(char *s, int k) {
- int nd = root, len = strlen(s);
- for (int i = 0; i < len; ++i) {
- int c = s[i] - 'a';
- if (!trie[c][nd])
- trie[c][nd] = ++tot;
- nd = trie[c][nd];
- }
- ++end[nd];
- id[k] = nd;
- }
- void Build() {
- for (int c = 0; c < 26; ++c)
- trie[c][0] = root;
- queue<int> que;
- que.push(root);
- while (!que.empty()) {
- int nd = que.front(); que.pop();
- for (int c = 0; c < 26; ++c) {
- int son = trie[c][nd];
- if (!son) {
- trie[c][nd] = trie[c][pi[nd]];
- continue;
- }
- int nxt = pi[nd];
- while (nxt && !trie[c][nxt])
- nxt = pi[nxt];
- pi[son] = trie[c][nxt];
- last[son] = end[pi[son]] ? pi[son] : last[pi[son]];
- que.push(son);
- }
- }
- }
- void dfs(int u) {
- for (int i = head[u]; i; i = edge[i].nxt) {
- int v = edge[i].to;
- dfs(v);
- cnt[u] += cnt[v];
- }
- }
- void Match(char *s) {
- register int nd = root; int len = strlen(s);
- for (int i = 0; i < len; ++i) {
- int c = s[i] - 'a';
- nd = trie[c][nd];
- ++cnt[nd];
- }
- for (int i = 2; i <= tot; ++i)
- Insert_edge(pi[i], i); //建fail树
- dfs(root); //统计子树大小
- }
- } using namespace ACAM;
- int main() {
- ios::sync_with_stdio(0);
- cin.tie(0);
- int n;
- cin >> n;
- for (int i = 1; i <= n; ++i) {
- cin >> T;
- Insert(T, i);
- }
- Build();
- cin >> s;
- Match(s);
- for (int i = 1; i <= n; ++i)
- printf("%d\n", cnt[id[i]]);
- return 0;
- }
【模版 Luogu P3808/P3796/P5357】AC自动机(简论)的更多相关文章
- AC自动机例题
P3808 [模板]AC自动机(简单版) [题目描述] 给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过. #include<bits/stdc++.h> using name ...
- 洛谷P3808 & P3796 AC自动机模板
题目:P3808:https://www.luogu.org/problemnew/show/P3808 P3796:https://www.luogu.org/problemnew/show/P37 ...
- P3808 【模版】AC自动机(简单版)
题目背景 这是一道简单的AC自动机模版题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 题目描述 给定n个模式串和1个文本串,求有多少个模式串在文本 ...
- luogu P3796【模板】AC自动机(加强版)
嘟嘟嘟 这个和某谷的AC自动机模板简单版差不多. 但还是要注意几点的: 1.这个是统计出现次数,而不是是否出现,所以在查询的时候加上这个节点的val后,不能把val标记为-1.那么也就可以说查询的时间 ...
- luogu P3808 【模板】AC自动机(简单版)
题目背景 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 管理员提示:本题数据内有重复的单词,且重复单词应该计算多次, ...
- AC自动机(模板) LUOGU P3808
传送门 解题思路 AC自动机,是解决多模匹配问题的算法,是字典树与kmp结合的算法,可以解决许多子串在文本串中出现的次数等信息.关键是实现一个fail指针,是指向更靠上的前缀相同字母,从而可以实现在文 ...
- P3796 【模板】AC自动机(加强版)
P3796 [模板]AC自动机(加强版) https://www.luogu.org/problemnew/show/P3796 题目描述 有NN个由小写字母组成的模式串以及一个文本串TT.每个模式串 ...
- [算法模版]AC自动机
[算法模版]AC自动机 基础内容 板子不再赘述,OI-WIKI有详细讲解. \(query\)函数则是遍历文本串的所有位置,在文本串的每个位置都沿着\(fail\)跳到根,将沿途所有元素答案++.意义 ...
- 洛谷 P3796 【模板】AC自动机(加强版)(AC自动机)
题目链接:https://www.luogu.com.cn/problem/P3796 AC自动机:复杂度$O( (N+M)\times L )$,N为模式串个数,L为平均长度,M为文章长度. ins ...
随机推荐
- 将书法字体制作成pcb库文件,并使用该字体作为logo印制在自己设计的电路板上。
本文主要介绍,如何将写在纸张上的书法制作成pcb库文件,以达到如下效果: 形成具有镂空效果的标记,印制在PCB电路板上,一图logo位于top overlayer,是镂空丝印,二图位于top laye ...
- 【DeepLearning】基本概念:卷积、池化、Backpropagation
终于有了2个月的空闲时间,给自己消化沉淀,希望别有太多的杂事打扰.在很多课程中,我都学过卷积.池化.dropout等基本内容,但目前在脑海中还都是零散的概念,缺乏整体性框架,本系列博客就希望进行一定的 ...
- STM32入门系列-GPIO结构
已经了解了STM32 GPIO的基本概念及引脚分类.现在来看下STM32 GPIO内部的结构是怎样的.IO端口位的基本结构如下图所示. 从图中可以看出GPIO内部结构还是比较复杂的,只要将这张GPIO ...
- 工业4G DTU是什么和普通DTU有什么不同
4G DTU作为一种物联网远程数据传输设备,现今在世界范围内都有非常广泛的应用,根据应用场景的不同,对4G DTU设备支持的功能,硬件设施的要求都有不同,特别是在工业现场,对4G DTU设备的要求非常 ...
- Scala-1-字符处理
// s插值val s = s"a = $a, b = $b"val s = s"a = ${a*2}, b = ${b*3}" // 顶格 及 插值val s ...
- Polyglot Translators: Let's do i18n easier! 一款国际化插件小助手!
在做国际化文本有关的工作时, 是否厌倦了在不同应用或者网页之间频繁地切换进行中文, 繁体, 英文甚至韩文日文的文本翻译工作? 好吧, 我就是受不了频繁在进行文本字符串的转换, 还得跑到百度翻译上面搜索 ...
- 自动化测试之Selenium篇(一):环境搭建
当前无论找工作或者是实际项目应用,自动化测试扮演着非常重要的角色,今天我们来学习下Selenium的环境搭建 Selenium简述 Selenium是一个强大的开源Web功能测试工具系列 可进行读入测 ...
- yum安装出现被锁定的报错
问题:在使用#yum install XXX 命令的时候,出现yum.pid 已被锁定的提示,无法进行yum 安装 解决: 使用# rm -f /var/run/yum.pid 命令删除该进程即可
- iNeuOS工业互联平台,WEB组态(iNeuView)增加工程视图导入、导出功能,及优化和修复,发布:v3.2.1版本
目 录 1. 概述... 2 2. 平台演示... 2 3. 导出组态工程文件... 2 4. 导入组态工程文件... 3 1. 概述 iNe ...
- Elasticsearch 注册windows服务后,服务启动失败,意外终止
直接双击elasticsearch.bat可以成功启动,注册成服务后就启动失败 从网上查找问题,发现是jdk版本的问题,用ES自带的jdk就可以启动成功. 默认ES会先找JAVA_HOME环境变量,如 ...