浙江集训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)

二、代码

  模版一:统计出现模式串的个数

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <queue>
  5. #define BUG puts("findone")
  6. #define maxn 1000000 + 10
  7. template <typename T>
  8. void read(T &x) {
  9. x = 0;
  10. int f = 1;
  11. char ch = getchar();
  12. while (!isdigit(ch)) {
  13. if (ch == '-')
  14. f = -1;
  15. ch = getchar();
  16. }
  17. while (isdigit(ch)) {
  18. x = x * 10 + (ch ^ 48);
  19. ch = getchar();
  20. }
  21. x *= f;
  22. return;
  23. }
  24. using namespace std;
  25. char s[maxn];
  26. namespace ACAM {
  27. int trie[26][maxn], pi[maxn], cnt[maxn], last[maxn];
  28. const int root(1);
  29. int tot = 1;
  30. void Insert(char *s) {
  31. int nd = root, len = strlen(s);
  32. for (int i = 0; i < len; ++i) {
  33. int c = s[i] - 'a';
  34. if (!trie[c][nd])
  35. trie[c][nd] = ++tot;
  36. nd = trie[c][nd];
  37. }
  38. ++cnt[nd];
  39. }
  40. void Build_ACAM() {
  41. for (int i = 0; i < 26; ++i)
  42. trie[i][0] = root;
  43. pi[root] = 0;
  44. queue<int> que;
  45. que.push(root);
  46. while (!que.empty()) {
  47. int nd = que.front();
  48. que.pop();
  49. for (int c = 0; c < 26; ++c) {
  50. if (!trie[c][nd]) {
  51. //                  trie[c][nd] = trie[c][pi[nd]];  //这句话就是trie图优化,这题不用它反而跑得更快……
  52. continue;
  53. }
  54. int son = trie[c][nd], nxt = pi[nd];
  55. while (nxt && !trie[c][nxt])
  56. nxt = pi[nxt];
  57. pi[son] = trie[c][nxt];
  58. last[son] = cnt[pi[son]] ? pi[son] : last[pi[son]];  //last优化,它在三道题中都很优秀
  59. que.push(son);
  60. }
  61. }
  62. }
  63. int Match(char *s) {
  64. int len = strlen(s), nd = root, ans = 0;
  65. for (int i = 0; i < len; ++i) {
  66. int c = s[i] - 'a';
  67. while (nd && !trie[c][nd]) {  //如果加了trie图优化就不用每次跳fail边来找后缀,因为trie图优化直接记录可匹配的下一个后缀
  68. nd = pi[nd];
  69. }
  70. nd = trie[c][nd];
  71. for (int t = nd; t && ~cnt[t]; t = last[t])
  72. ans += cnt[t], cnt[t] = -1;
  73. }
  74. return ans;
  75. }
  76. } using namespace ACAM;
  77. int main() {
  78. //  freopen("testdata.txt", "r", stdin);
  79. //  freopen("ans.txt", "w", stdout);
  80. int n;
  81. scanf("%d", &n);
  82. for (int i = 1; i <= n; ++i) {
  83. scanf("%s", s);
  84. Insert(s);
  85. }
  86. Build_ACAM();
  87. scanf("%s", s);
  88. cout << Match(s);
  89. return 0;
  90. }

模版二:AC自动机(加强版):多组数据,输出出现最多的串的出现次数,按输入顺序输出这些串。

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <queue>
  5. #include <vector>
  6. #define BUG puts("findone")
  7. #define maxn 70 * 150 + 10
  8. template <typename T>
  9. void read(T &x) {
  10. x = 0;
  11. int f = 1;
  12. char ch = getchar();
  13. while (!isdigit(ch)) {
  14. if (ch == '-')
  15. f = -1;
  16. ch = getchar();
  17. }
  18. while (isdigit(ch)) {
  19. x = x * 10 + (ch ^ 48);
  20. ch = getchar();
  21. }
  22. x *= f;
  23. return;
  24. }
  25. using namespace std;
  26. char s[1000010], T[151][80];
  27. namespace ACAM {
  28. int trie[26][maxn], pi[maxn], cnt[maxn], last[maxn], sum[maxn], id[maxn];
  29. const int root(1);
  30. int tot = 1;
  31. void Insert(char *s, int pos) {
  32. int nd = root, len = strlen(s);
  33. for (int i = 0; i < len; ++i) {
  34. int c = s[i] - 'a';
  35. if (!trie[c][nd])
  36. trie[c][nd] = ++tot;
  37. nd = trie[c][nd];
  38. }
  39. ++cnt[nd], id[nd] = pos;  //id数组的意义是记录每个节点(状态)在原输入顺序中所对应的串
  40. }
  41. void Build_ACAM() {
  42. for (int i = 0; i < 26; ++i)
  43. trie[i][0] = root;
  44. pi[root] = 0;
  45. queue<int> que;
  46. que.push(root);
  47. while (!que.empty()) {
  48. int nd = que.front();
  49. que.pop();
  50. for (int c = 0; c < 26; ++c) {
  51. if (!trie[c][nd]) {
  52. trie[c][nd] = trie[c][pi[nd]];
  53. continue;
  54. }//优化位置
  55. int son = trie[c][nd], nxt = pi[nd];
  56. while (nxt && !trie[c][nxt])
  57. nxt = pi[nxt];
  58. pi[son] = trie[c][nxt];
  59. last[son] = cnt[pi[son]] ? pi[son] : last[pi[son]];
  60. que.push(son);
  61. }
  62. }
  63. }
  64. void Match(char *s) {
  65. int len = strlen(s), nd = root;
  66. for (int i = 0; i < len; ++i) {
  67. int c = s[i] - 'a';
  68. //          while (nd && !trie[c][nd])
  69. //              nd = pi[nd];
  70. nd = trie[c][nd];
  71. for (int t = nd; t; t = last[t])
  72. if (cnt[t])
  73. ++sum[t];
  74. }
  75. vector<int> ans;
  76. for (int i = 1; i <= tot; ++i)
  77. if (ans.empty() || sum[i] == sum[ans.front()])
  78. ans.push_back(i);
  79. else if (sum[i] > sum[ans.front()]) {
  80. ans.clear();
  81. ans.push_back(i);
  82. }
  83. printf("%d\n", sum[ans.front()]);
  84. for (int i = 0; i < ans.size(); ++i)
  85. puts(T[id[ans[i]]]);
  86. }
  87. } using namespace ACAM;
  88. int main() {
  89. //  freopen("testdata.txt", "r", stdin);
  90. //  freopen("ans.txt", "w", stdout);
  91. ios::sync_with_stdio(0);  //某种加快iostream的黑科技 据称读入字符串飞快
  92. cin.tie(0);
  93. while (19260817) {
  94. int n;
  95. cin >> n;
  96. if(n == 0) break;
  97. tot = 1;
  98. memset(trie, 0, sizeof(trie));
  99. memset(sum, 0, sizeof(sum));
  100. memset(cnt, 0, sizeof(cnt));
  101. memset(pi, 0, sizeof(pi));
  102. memset(last, 0, sizeof(last));
  103. memset(id, 0, sizeof(id));
  104. for (int i = 1; i <= n; ++i) {
  105. cin >> T[i];
  106. Insert(T[i], i);
  107. }
  108. Build_ACAM();
  109. cin >> s;
  110. Match(s);
  111. }
  112. return 0;
  113. }

模版三、AC自动机(二次加强版):统计每个模式串出现的次数。一开始的策略是每到一个位置就暴力跳last边来找后缀,但是时间只有1000ms,T掉了几个点。参看题解给出的解法是:统计每个状态的出现次数,然后从fail[u]向u连边,构成一棵树。这棵树被称作fail树,满足每个节点的祖先都是它的后缀。这样,每个模式串的出现次数就是它自己的出现次数+以它为后缀的串的出现次数,也就是以它为根的子树的大小。trie树上某状态的祖先则是它的前缀。fail树的性质很好,也具有广泛的应用。

  1. #include <iostream>
  2. #include <cstdio>
  3. #include <cstring>
  4. #include <queue>
  5. #define maxs 2000010
  6. #define maxn 200010
  7. using namespace std;
  8. char T[maxn], s[maxs];
  9. int head[maxn], top;
  10. struct E {
  11. int to, nxt;
  12. } edge[maxn];
  13. void Insert_edge(int u, int v) {
  14. edge[++top] = (E) {v, head[u]};
  15. head[u] = top;
  16. }
  17. namespace ACAM {
  18. int trie[26][maxn], tot = 1, cnt[maxn], pi[maxn], last[maxn], end[maxn];
  19. int id[maxn];
  20. const int root(1);
  21. void Insert(char *s, int k) {
  22. int nd = root, len = strlen(s);
  23. for (int i = 0; i < len; ++i) {
  24. int c = s[i] - 'a';
  25. if (!trie[c][nd])
  26. trie[c][nd] = ++tot;
  27. nd = trie[c][nd];
  28. }
  29. ++end[nd];
  30. id[k] = nd;
  31. }
  32. void Build() {
  33. for (int c = 0; c < 26; ++c)
  34. trie[c][0] = root;
  35. queue<int> que;
  36. que.push(root);
  37. while (!que.empty()) {
  38. int nd = que.front(); que.pop();
  39. for (int c = 0; c < 26; ++c) {
  40. int son = trie[c][nd];
  41. if (!son) {
  42. trie[c][nd] = trie[c][pi[nd]];
  43. continue;
  44. }
  45. int nxt = pi[nd];
  46. while (nxt && !trie[c][nxt])
  47. nxt = pi[nxt];
  48. pi[son] = trie[c][nxt];
  49. last[son] = end[pi[son]] ? pi[son] : last[pi[son]];
  50. que.push(son);
  51. }
  52. }
  53. }
  54. void dfs(int u) {
  55. for (int i = head[u]; i; i = edge[i].nxt) {
  56. int v = edge[i].to;
  57. dfs(v);
  58. cnt[u] += cnt[v];
  59. }
  60. }
  61. void Match(char *s) {
  62. register int nd = root; int len = strlen(s);
  63. for (int i = 0; i < len; ++i) {
  64. int c = s[i] - 'a';
  65. nd = trie[c][nd];
  66. ++cnt[nd];
  67. }
  68. for (int i = 2; i <= tot; ++i)
  69. Insert_edge(pi[i], i);  //建fail树
  70. dfs(root);  //统计子树大小
  71. }
  72. } using namespace ACAM;
  73. int main() {
  74. ios::sync_with_stdio(0);
  75. cin.tie(0);
  76. int n;
  77. cin >> n;
  78. for (int i = 1; i <= n; ++i) {
  79. cin >> T;
  80. Insert(T, i);
  81. }
  82. Build();
  83. cin >> s;
  84. Match(s);
  85. for (int i = 1; i <= n; ++i)
  86. printf("%d\n", cnt[id[i]]);
  87. return 0;
  88. }

【模版 Luogu P3808/P3796/P5357】AC自动机(简论)的更多相关文章

  1. AC自动机例题

    P3808 [模板]AC自动机(简单版) [题目描述] 给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过. #include<bits/stdc++.h> using name ...

  2. 洛谷P3808 & P3796 AC自动机模板

    题目:P3808:https://www.luogu.org/problemnew/show/P3808 P3796:https://www.luogu.org/problemnew/show/P37 ...

  3. P3808 【模版】AC自动机(简单版)

    题目背景 这是一道简单的AC自动机模版题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 题目描述 给定n个模式串和1个文本串,求有多少个模式串在文本 ...

  4. luogu P3796【模板】AC自动机(加强版)

    嘟嘟嘟 这个和某谷的AC自动机模板简单版差不多. 但还是要注意几点的: 1.这个是统计出现次数,而不是是否出现,所以在查询的时候加上这个节点的val后,不能把val标记为-1.那么也就可以说查询的时间 ...

  5. luogu P3808 【模板】AC自动机(简单版)

    题目背景 这是一道简单的AC自动机模板题. 用于检测正确性以及算法常数. 为了防止卡OJ,在保证正确的基础上只有两组数据,请不要恶意提交. 管理员提示:本题数据内有重复的单词,且重复单词应该计算多次, ...

  6. AC自动机(模板) LUOGU P3808

    传送门 解题思路 AC自动机,是解决多模匹配问题的算法,是字典树与kmp结合的算法,可以解决许多子串在文本串中出现的次数等信息.关键是实现一个fail指针,是指向更靠上的前缀相同字母,从而可以实现在文 ...

  7. P3796 【模板】AC自动机(加强版)

    P3796 [模板]AC自动机(加强版) https://www.luogu.org/problemnew/show/P3796 题目描述 有NN个由小写字母组成的模式串以及一个文本串TT.每个模式串 ...

  8. [算法模版]AC自动机

    [算法模版]AC自动机 基础内容 板子不再赘述,OI-WIKI有详细讲解. \(query\)函数则是遍历文本串的所有位置,在文本串的每个位置都沿着\(fail\)跳到根,将沿途所有元素答案++.意义 ...

  9. 洛谷 P3796 【模板】AC自动机(加强版)(AC自动机)

    题目链接:https://www.luogu.com.cn/problem/P3796 AC自动机:复杂度$O( (N+M)\times L )$,N为模式串个数,L为平均长度,M为文章长度. ins ...

随机推荐

  1. CF1336 Linova and Kingdom

    题面 给定 n 个节点的有根树,根是 1 号节点. 你可以选择 k 个节点将其设置为工业城市,其余设置为旅游城市. 对于一个工业城市,定义它的幸福值为工业城市到根的路径经过的旅游城市的数量. 你需要求 ...

  2. elk之插件部署 (实操三)

    一.插件安装 下载head以及node软件包: elasticsearch-head.tar.gz node-v8.12.0-linux-x64.tar.gz 找不到这两个包的评论下留言或私我 解压软 ...

  3. 仅用六种字符来完成Hello World,你能做到吗?

    Hello World 对于每一个开发者来说都不陌生,因为在我们学习任何一个语言或框架的时候,都会有一个Hello World的案例来帮助我们快速入门. 如果我们使用JavaScript来输出Hell ...

  4. C语言100题集合005-删除一维数组中所有相同的数,使之只剩一个

    系列文章<C语言经典100例>持续创作中,欢迎大家的关注和支持. 喜欢的同学记得点赞.转发.收藏哦- 后续C语言经典100例将会以pdf和代码的形式发放到公众号 欢迎关注:计算广告生态 即 ...

  5. “谈谈MySQL的基数统计”

    ** 目录 推荐阅读原文链接 一.基数是啥? 二.InnoDB更新基数的时机? 三.基数是估算出来 四.持久化基数 四.如何主动更新基数? Hi,大家好!我是白日梦. 今天我要跟你分享的话题是:&qu ...

  6. egit版本对应关系。

    egit版本对应关系. http://wiki.eclipse.org/EGit/FAQ#What_versions_of_Eclipse_does_EGit_target.3F

  7. startup乱码解决方法

    1.用记事本方式打开:‪apache-tomcat-8.5.59\conf\logging.properties 2.使用快捷键(Ctrl+H)把UTF-8全部替换为:GBK,进行保存(Ctrl+s) ...

  8. Mysql之存储过程与存储函数

    1 存储过程 1.1 什么是存储过程 存储过程是一组为了完成某项特定功能的sql语句集,其实质上就是一段存储在数据库中的代码,他可以由声明式的sql语句(如CREATE,UPDATE,SELECT等语 ...

  9. shell编程之trap命令

    trap command  signal trap捕获信号(软中断),command一般是linux命令 若为' '表示发生陷阱时为空指令,'-'表示发生陷阱时采用缺省指令 signal: HUP(1 ...

  10. binary hacks读数笔记(ld 链接讲解 二)

    这块将介绍一下ld链接命令的具体使用.ld的作用:ld是GNU binutils工具集中的一个,是众多Linkers(链接器)的一种.完成的功能自然也就是链接器的基本功能:把各种目标文件和库文件链接起 ...