AC自动机--summer-work之我连模板题都做不出

这章对现在的我来说有点难,要是不写点东西,三天后怕是就一无所有了。

但写这个没有营养的blog的目的真的不是做题或提升,只是学习学习代码和理解一些概念。

现在对AC自动机的理解还十分浅薄,这里先贴上目前我看过的文章:

  1. 深入理解Aho-Corasick自动机算法
  2. AC 自动机学习笔记

AC自动机相比Trie多了失配边,结点到结点间的状态转移,结点到根的状态转移。

这里fail的定义是:使当前字符失配时跳转到另一段从root开始每一个字符都与当前已匹配字符段某一个后缀完全相同且长度最大的位置继续匹配。

A - Keywords Search

题意:给多个模式串,求文本串中有多少模式串是子串。

这里直接贴上kuangbin大佬的模板。

  1 #include <cstdio>
  2 #include <algorithm>
  3 #include <iostream>
  4 #include <cstring>
  5 #include <queue>
  6 using namespace std;
  7 struct Trie{
  8     int next[500010][26], fail[500010], end[500010];
  9     int root, L;
 10     int newnode(){
 11         for(int i=0; i<26; i++){
 12             next[L][i] = -1;
 13         }
 14         end[L++] = 0;
 15         return L-1;
 16     }
 17     void init(){
 18         L = 0;
 19         root = newnode();
 20     }
 21     void insert(char buf[]){
 22         int len = strlen(buf);
 23         int now = root;
 24         for(int i=0; i<len; i++){
 25             if(next[now][buf[i]-‘a‘] == -1){
 26                 next[now][buf[i]-‘a‘] = newnode();
 27             }
 28             now = next[now][buf[i]-‘a‘];
 29         }
 30         end[now]++;
 31     }
 32     void build(){
 33         queue<int> Q;
 34         fail[root] = root;
 35         for(int i=0; i<26; i++){
 36             if(next[root][i] == -1){
 37                 next[root][i] = root;
 38             }else{
 39                 fail[next[root][i]] = root;
 40                 Q.push(next[root][i]);
 41             }
 42         }
 43         while( !Q.empty() ){
 44             int now = Q.front();
 45             Q.pop();
 46             for(int i=0; i<26; i++){
 47                 if(next[now][i] == -1){
 48                     next[now][i] = next[fail[now]][i];
 49                 }else{
 50                     fail[next[now][i]] = next[fail[now]][i];
 51                     Q.push(next[now][i]);
 52                 }
 53             }
 54         }
 55     }
 56     int query(char buf[]){
 57         int len = strlen(buf);
 58         int now = root;
 59         int res = 0;
 60         for(int i=0; i<len; i++){
 61             now = next[now][buf[i]-‘a‘];
 62             int temp = now;
 63             while(temp != root){
 64                 res += end[temp];
 65                 end[temp] = 0;
 66                 temp = fail[temp];
 67             }
 68         }
 69         return res;
 70     }
 71     void debug(){
 72         for(int i=0; i<L; i++){
 73             cout << "id = " << i << "   " << "fail = " << fail[i] << "    " << "end = " << end[i] << endl;
 74             for(int j=0; j<26; j++){
 75                 cout << next[i][j] << "   ";
 76             }
 77             cout << endl;
 78         }
 79     }
 80 };
 81 char buf[1000010];
 82 Trie ac;
 83 int main(){
 84     freopen("in.txt", "r", stdin);
 85     int T;
 86     scanf("%d", &T);
 87     while(T--){
 88         int n;
 89         scanf("%d", &n);
 90         ac.init();
 91         for(int i=0; i<n; i++){
 92             scanf("%s", buf);
 93             ac.insert(buf);
 94         }
 95         ac.build();
 96         scanf("%s", buf);
 97         cout << buf << endl;
 98         printf("%d\n", ac.query(buf));
 99     }
100     return 0;
101 }

C - L语言

我把学习心得放在这里了

D - Computer Virus on Planet Pandora

题意:给一个文本串,求其正反两个串中出现了几个模式串。

这里AC自动机中query()操作的作用:查询Trie中有几个前缀出现在文本串中。

方法是遍历Trie树找val=1的点即字符串最后一个字符,然后计数,同时要通过fail找回边,这里就是kmp的意味了。

所以才有人说AC自动机 = Trie+Kmp。

  1 #include <cstdio>
  2 #include <algorithm>
  3 #include <iostream>
  4 #include <cstring>
  5 #include <queue>
  6 using namespace std;
  7 #define mst(s, t) memset(s, 0, sizeof(s));
  8 struct Trie{
  9     int ch[250110][26], fail[250010], val[250110];
 10     int res, sz;
 11     inline void init(){ sz=1; mst(ch[0], 0); mst(val, 0); }
 12     inline int idx(char c) { return c-‘A‘; }
 13     inline void insert(char *s){
 14         int u = 0, n = strlen(s);
 15         for(int i=0; i<n; i++){
 16             int c = idx(s[i]);
 17             if(!ch[u][c]){
 18                 mst(ch[sz], 0);
 19                 ch[u][c] = sz++;
 20             }
 21             u = ch[u][c];
 22         }
 23         val[u] = 1;
 24     }
 25     inline void build(){
 26         queue<int> Q;
 27         int u;  fail[0] = 0;
 28         for(int i=0; i<26; i++){
 29             u = ch[0][i];
 30             if(u){ fail[u]=0;     Q.push(u); }
 31         }
 32         while( !Q.empty() ){
 33             int now = Q.front();    Q.pop();
 34             for(int i=0; i<26; i++){
 35                 u = ch[now][i];
 36                 if(!u){ ch[now][i] = ch[fail[now]][i]; continue; }
 37                 Q.push(u);
 38                 int v = fail[now];
 39                 while(v && !ch[v][i]) v=fail[v];
 40                 fail[u] = ch[v][i];
 41             }
 42         }
 43     }
 44     inline int query(char *buf, int len){
 45         int u = 0, res = 0;
 46         for(int i=0; i<len; i++){
 47             int c = idx(buf[i]);
 48             u = ch[u][c];
 49             int temp = u;
 50             //有几个前缀出现在文本串中
 51             while(temp && val[temp]!=0){
 52                 res += val[temp];
 53                 val[temp] = 0;
 54                 temp = fail[temp];
 55             }
 56         }
 57         return res;
 58     }
 59 };
 60 char tmp[5100010], buf[5100010];
 61 Trie ac;
 62 int main(){
 63 //    freopen("in.txt", "r", stdin);
 64     int T;
 65     scanf("%d", &T);
 66     while(T--){
 67         ac.init();
 68         int n;
 69         scanf("%d", &n);
 70         for(int i=0; i<n; i++){
 71             scanf("%s", buf);
 72             ac.insert(buf);
 73         }
 74         ac.build();
 75         scanf("%s", tmp);
 76         int tlen = strlen(tmp), blen=0;
 77         for(int i=0; i<tlen; i++){
 78             if(tmp[i]==‘[‘){
 79                 i++;
 80                 int cnt = 0;
 81                 while(tmp[i] >= ‘0‘ && tmp[i] <= ‘9‘){
 82                     cnt = cnt*10 + tmp[i++]-‘0‘;
 83                 }
 84                 for(int k=0; k<cnt; k++){
 85                     buf[blen++] = tmp[i];
 86                 }
 87                 i++;
 88             }else{
 89                 buf[blen++] = tmp[i];
 90             }
 91         }
 92         tmp[blen] = ‘\0‘;
 93         for(int i=0; i<blen; i++){
 94             tmp[i] = buf[blen-i-1];
 95         }
 96         buf[blen] = ‘\0‘;
 97         printf("%d\n", ac.query(buf, blen) + ac.query(tmp, blen));
 98     }
 99     return 0;
100 }

E - Family View

题意:找出文本串中所有模式串。

AC自动机的变化在insert()和find(),比较难想的应该是如何结合题意利用find()。

上题是计数模式串出现在文本串中的个数,而这道题则是每个模式串出现在文本串中的位置。

那么重点就是如何标记这些位置。

这题我用的是训练指南上的代码风。

  1 #include <cstdio>
  2 #include <iostream>
  3 #include <cstring>
  4 #include <algorithm>
  5 #include <queue>
  6 #include <cctype>
  7 using namespace std;
  8 #define mst(s, t) memset(s, t, sizeof(s))
  9 const int maxnode = 1e6+10;
 10 const int sigma_size = 26;
 11 struct Trie{
 12     int ch[maxnode][sigma_size], val[maxnode], f[maxnode], last[maxnode], sz;
 13     int cnt[maxnode], dis[maxnode];
 14     inline void init(){ sz=1; mst(ch[0], 0); mst(val, 0); mst(cnt, 0); }
 15     inline int idx(char x){ return tolower(x)-‘a‘; }
 16     inline void insert(char *s){
 17         int u = 0, n = strlen(s);
 18         for(int i=0; i<n; i++){
 19             int c = idx(s[i]);
 20             if(!ch[u][c]){
 21                 mst(ch[sz], 0);
 22                 ch[u][c] = sz++;
 23             }
 24             u = ch[u][c];
 25         }
 26         val[u] = 1;
 27         dis[u] = n;
 28     }
 29     inline void get_fail(){
 30         queue<int> Q;
 31         int u;   f[0] = 0;
 32         for(int c=0; c<sigma_size; c++){
 33             u = ch[0][c];
 34             if(u) { last[u] = f[u]=0; Q.push(u); }
 35         }
 36         while(!Q.empty()){
 37             int now = Q.front(); Q.pop();
 38             for(int c=0; c<sigma_size; c++){
 39                 u = ch[now][c];
 40                 if(!u)  { ch[now][c]=ch[f[now]][c]; continue; }
 41                 Q.push(u);
 42                 int v = f[now];
 43                 while(v && !ch[v][c]) v = f[v];
 44                 f[u] = ch[v][c];
 45                 last[u] = val[f[u]] ? f[u] : last[f[u]];
 46                 //{(he), (she)}5  --> {(he)}2
 47                 //last[5] = val[2]? 2 : last[f[2]]
 48                 //last[5] =   1   ? 2 : 0
 49                 //last[i]:表示沿着失配指针往回走时遇到的下一个单词结点编号
 50             }
 51         }
 52     }
 53     inline void find(char *s){
 54         int n = strlen(s), u = 0;
 55         for(int i=0; i<n; i++){
 56             if(!isalpha(s[i]))continue;  //这里要写,空格等乱七八糟的东西太多
 57             int c = idx(s[i]);
 58             u = ch[u][c];
 59             if(val[u])  prin(i, u);               //在字符串中找到前缀树中的内容后对字符串中的信息进行标记
 60             else if(last[u]) prin(i, last[u]);
 61         }
 62     }
 63     inline void prin(int i, int j){
 64         if(j){
 65             ++cnt[i+1];
 66             --cnt[i+1-dis[j]];   //[i+1-len, i+1)所有字符要forbid,故标记首尾
 67             //cout << "i = " << i<< "    last["<<j<<"] = "<<last[j]<< endl;
 68             /*
 69              * 这里last数组和fail数组回跳的方式有什么区别 和 它是如何提高效率的,
 70              * 若有前辈知道,望直接指教,感激不尽。
 71             */
 72             prin(i, last[j]);
 73         }
 74     }
 75     /*
 76     inline void show(){
 77         for(int i=0; i<sz; i++){
 78             cout<<"last["<<i<<"] = "<<last[i]<<"       f["<<i<<"] = "<<f[i]<<endl;
 79         }
 80     }
 81     */
 82 };
 83 Trie ac;
 84 char s[maxnode];
 85 int main(){
 86     freopen("in.txt", "r", stdin);
 87     int t;
 88     scanf("%d", &t);
 89     while(t--){
 90         ac.init();
 91         int n;
 92         scanf("%d", &n);
 93         while(n--){
 94             scanf("%s", s);
 95             ac.insert(s);
 96         }
 97         ac.get_fail();
 98         getchar();
 99         fgets(s, maxnode, stdin);
100         ac.find(s);
101         int ans = 0, len = strlen(s);
102         for(int i=0; i<len; i++){
103             ans += ac.cnt[i];
104             if(ans < 0)  putchar(‘*‘);
105             else putchar(s[i]);
106         }
107     }
108     return 0;
109 }

还有就是我有一个疑惑想了很久没有解决,也找了万老师解惑,虽然并没有得到很令我心服的答案,但依然很感谢万老师的指导。

训练指南245页有关last指针的分析,他说后缀链接last[j]是结点j沿着失配指针往回走。我的疑惑是:这个last指针和fail有什么区别,或是二者回跳方式有何不同。如果有哪位聚聚或老师对这个问题有过思考请不吝指教,或指导下学习方式等,本菜鸡定感激不尽。

原文地址:https://www.cnblogs.com/seaupnice/p/9503188.html

时间: 2024-11-08 07:48:01

AC自动机--summer-work之我连模板题都做不出的相关文章

AC自动机(附洛谷P3769模板题)

首先,介绍一下AC自动机(Aho-Corasick automaton),是一种在一个文本串中寻找每一个已给出的模式串的高效算法. 在学习AC自动机之前,你需要先学习Trie树和KMP算法,因为AC自动机正式利用并结合了两者的思想. 说到实际的不同,其实AC自动机只是在Trie树上引入了一个类似KMP中next数组的东西叫做Fail指针. 对于每一个节点,Fail指针指向该节点所代表的字符串中,次长的.在Trie树中存在的后缀(因为最长的在Trie树种存在的后缀就是其本身)所代表的节点. 举例:

AC自动机入门

Aho-Corasick automaton,该算法在1975年产生于贝尔实验室,是著名的多模式匹配算法之一. KMP算法很好的解决了单模式匹配问题,如果有了字典树的基础,我们可以完美的结合二者解决多模式匹配问题. 在KMP算法中,我们预先根据待匹配串自身的信息得到失配指针,使得在每次匹配不成功后,可以不再去处理模式串的已匹配过的部分,进而使得复杂度降为O(N). 对于多模式串匹配问题,当一个模式串与待匹配串不匹配时,失配指针可以指向任意一个串,这就需要我们利用字典树来组织所有模式串并得到失配指

【暑假】[实用数据结构] AC自动机

Aho-Corasick自动机 AC自动机用于解决文本一个而模板有多个的问题. 作者所给模板如下: 1 struct AhoCorasickAutomata { 2 int ch[MAXNODE][SIGMA_SIZE]; 3 int f[MAXNODE]; // fail函数 4 int val[MAXNODE]; // 每个字符串的结尾结点都有一个非0的val 5 int last[MAXNODE]; // 输出链表的下一个结点 6 int cnt[MAXS]; 7 int sz; 8 9

强势图解AC自动机

本篇文章主要详细介绍$AC$自动机的$fail$指针: 如果有什么不完善的地方,请联系我$qwq$ 前置知识: 1.建议学一下$kmp$算法 2.$Trie$ 导入: AC自动机是用来解决多模板匹配问题的,但是,如果就单纯的把每个模板串拼接在一起,或者单个单个匹配的话,肯定是会超时的,而它的思想是把所有的模式串建立一颗$Trie$,然后用文本串来匹配,那么我们就必须在这颗$Trie$树上进行快速跳转来优化,于是,AC自动机就诞生了 重点:fail指针到底是什么? 我们先来思考一个问题,加入我们按

hdu 1277 AC自动机入门

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1277 推荐一篇博客(看思路就可以,实现用的是java): https://www.cnblogs.com/nullzx/p/7499397.html 这是一道模板题,拿来练手,之前看了一篇博客,有点错误,但是hdu上面居然过了,最主要的是我在hdu上面三道AC自动机模板题都是这个错的代码,居然都过了,害的我纠结了一晚上,原来是数据太水了. 主要还是看上面的博客,写了点注释,不一定对,以后好拿来复习.

Trie图/AC自动机

made...发财谷题解基本是Trie图... 我永远喜欢AC自动机.jpg 下面是P3808 [模板]AC自动机(简单版) 的两种写法 Trie图 const int MAXN = 1000010; int t[MAXN][26], word[MAXN], fail[MAXN], cnt, q[MAXN]; inline void insert(char *s) { int u = 0; for (int i = 1; s[i]; ++i) { int v = s[i] - 'a'; int

hdu_3247_Resource Archiver(AC自动机+bfs+TSP)

题目链接:hdu_3247_Resource Archiver 题意: 有n个资源串,m个病毒串,现在要将所有的资源串整合到一个串内,并且这个串不能包括病毒串,问最短的串长为多少 题解: 将资源串和病毒串都插入到AC自动机中,分别做好标记,然后用bfs求出0节点和所有资源串互相的最短距离,最后就是一个TSP的状态压缩DP. 1 #include<bits/stdc++.h> 2 #define F(i,a,b) for(int i=a;i<=b;i++) 3 using namespac

poj 2778 AC自动机 + 矩阵快速幂

// poj 2778 AC自动机 + 矩阵快速幂 // // 题目链接: // // http://poj.org/problem?id=2778 // // 解题思路: // // 建立AC自动机,确定状态之间的关系,构造出,走一步 // 能到达的状态矩阵,然后进行n次乘法,就可以得到状态间 // 走n步的方法数. // 精髓: // 1):这个ac自动机有一些特别,根节点是为空串,然而 // 每走一步的时候,如果没法走了,这时候,不一定是回到根 // 节点,因为有可能单个的字符时病毒,这样

[C#] 逆袭——自制日刷千题的AC自动机攻克HDU OJ

前言 做过杭电.浙大或是北大等ACM题库的人一定对“刷题”不陌生,以杭电OJ为例:首先打开首页(http://acm.hdu.edu.cn/),然后登陆,接着找到“Online Exercise”下的“Problem Archive”,然后从众多题目中选择一个进行读题.构思.编程.然后提交.最后查看题解状态,如果AC了表示这一题被攻克了,否则就要重做了~一般情况下,“刷题”要求精神高度集中且经验丰富,否则很难成功AC,有时候甚至做一题要浪费半天的时间!(有时网速卡了,比抢火车票还要急!) 楼主在