ACM-ICPC模板


目录

  • 求有多少个模式串在文本串里出现过
  • 建fail树dfs求每个模式串在文本串中的出现次数
  • ac自动机fail树上dfs序建可持久化线段树

AC自动机是一种多模匹配算法
AC自动机的关键是:构建字典图实现自动跳转,构建失配指针实现多模式匹配。

形式上,AC 自动机基于由若干模式串构成的 Trie 树,并在此之上增加了一些 fail 边;本质上,AC 自动机是一个关于若干模式串的DFA(确定有限状态自动机),接受且仅接受以某一个模式串作为后缀的字符串。 并且,与一般自动机不同的,AC 自动机还有关于某个模式串的接受状态,也就是与某个模式串匹配(以某个模式串为后缀)的那些状态,即某个模式串在 Trie树上的终止节点在 fail 树上的整个子树。


给定n个模式串和1个文本串,求有多少个模式串在文本串里出现过。

玩命写注释

求有多少个模式串在文本串里出现过

/*luogu P3808 【模板】AC自动机(简单版)*/struct Trie{//trie树int fail;//失配指针int vis[30];//子结点的位置编号//vis数组实际上就是trie树里的trie[p][ch],指向的是该结点的子结点的编号int End;//trie树里用于标记有几个单词是以这个结点结尾的
}AC[N];int tot=0;//AC自动机里是从根节点0开始,trie树一般是从根节点1开始inline void build(string s){//构造tried树//基本的trie树的操作int len = s.length();int p = 0;//trie树的当前指针(当前结点)从根节点0开始for(int i = 0;i < len;++i){if(AC[p].vis[s[i]-'a'] == 0)//tire树的老规矩(不存在这个结点)AC[p].vis[s[i]-'a'] = ++tot;//不存在就构造一个新结点p = AC[p].vis[s[i]-'a'];//往下走}AC[p].End++;//标记每一个单词的结尾//因为最后要求的是有多少个单词。要计算重复的
}void Get_fail(){//构造失配指针利用bfs 遍历queue<int>Q;// 队列存for(int i= 0;i < 26;++i){//处理第二层的失配指针(26个字母遍历一遍)if(AC[0].vis[i] != 0){//若存在这个点AC[AC[0].vis[i]].fail = 0;//第二层都要指向根节点Q.push(AC[0].vis[i]);//并将该结点编号压入队列}}while(!Q.empty()){//遍历所有的结点int u = Q.front();//每次取出队头Q.pop();for(int i = 0;i < 26;++i){//枚举所有可能的子结点(26个字母)if(AC[u].vis[i]!=0){//若存在这个字母的子结点AC[AC[u].vis[i]].fail = AC[AC[u].fail].vis[i];//注意是vis[i],一定是指向与该结点字母相同的结点,若不存在就会是指向0(根节点)//让当前结点的子结点的失配指针fail指向,当前结点的失配指针fail所指向的//结点的,与当前结点的子结点字母相同的子结点Q.push(AC[u].vis[i]);//并把该结点压入队列里等待继续往下遍历}else//若不存在子结点AC[u].vis[i] = AC[AC[u].fail].vis[i];//因为不存在该结点,本身在trie树里会直接结束,但是会浪费时间//所以这里本身没有路可以走了,//但是这里让当前结点的子结点直接指向当前结点的失配指针fail指向的结点,//把路连上,下次遇上了可以直接走,这样将trie树构造成trie图,更加的优化、///其实就是相当于先预处理好,因为每一个点都要尽量连到另一个相同字母的结点,每一次遍历可能尽管这个结点没有这个字母//但是先把他连到结点的失配指针指向的那一个结点,说不定就有这个字母}}
}int AC_Query(string s){//AC自动机匹配int len = s.length();int p = 0,ans = 0;for(int i = 0;i < len;++i){p = AC[p].vis[s[i]-'a'];//往下一层走for(int k = p;k&&AC[k].End!=-1;k = AC[k].fail){//往后走,若没有到走过的结点或者到达根节点,就一直往失配指针指向的地方走ans += AC[k].End;//加上路径上的值AC[k].End = -1;//标记已走过(已经加过一遍了)}}return ans;
}int n;
string s;int main()
{scanf("%d",&n);memset(AC, 0, sizeof AC);//memset可以初始化结构体哇over(i,1,n){cin>>s;build(s);//建trie树(图)}AC[0].fail = 0;//结束标志(根节点的失配指针是一定指向自己的)Get_fail();//求出失配指针cin>>s;cout<<AC_Query(s)<<endl;return 0;
}

建fail树dfs求每个模式串在文本串中的出现次数

暴力跳 fail 边,会被类似于 aaaaa……aaaaa 这样的串卡掉。

正确的做法是建出 fail 树,记录自动机上的每个状态被匹配了几次,最后求出每个模式串在 Trie 上的终止节点在 fail 树上的子树总匹配次数就可以了,我们dfs建立好的fail树,从当前结点的失配指针指向的结点向该结点连边建fail树,这样我们dfs该树的时候可以保证每一条边(u,v)(u,v)(u,v),u到根节点之间的串等于v到根节点之间的串,那么该串的出现次数就是所有的边(u,v)(u,v)(u,v),的size之和。不会加重, 因为fail树是由fail指针边构成的,然而所有的点只会建立一个fail指针。例如有三个相同的模式串,只会有两个fail指针。

const int N = 200010, S = 2000010;queue<int>q;
struct trie{int fail;int vis[26];int ed;
}ac[N];int match[N];
int n;
int tot, sizes[N];
int head[N], nex[N], ver[N], cnt, num;void add(int x, int y){ver[cnt] = y;nex[cnt] = head[x];head[x] = cnt ++ ;
}void insert(int x, char s[]){int len = strlen(s);int p = 0;//根从0开始for(int i = 0; i < len; ++ i){int ch = s[i] - 'a';if(!ac[p].vis[ch])ac[p].vis[ch] = ++ tot;p = ac[p].vis[ch];}ac[p].ed ++ ;match[x] = p;//记录每个模式串在 Trie 树上的终止节点
}void get_fail(){for(int i = 0;i < 26; ++ i){if(ac[0].vis[i]){ac[ac[0].vis[i]].fail = 0;q.push(ac[0].vis[i]);}}while(q.size()){int p = q.front();q.pop();for(int i = 0;i < 26; ++ i){if(ac[p].vis[i]){ac[ac[p].vis[i]].fail = ac[ac[p].fail].vis[i];q.push(ac[p].vis[i]);}else ac[p].vis[i] = ac[ac[p].fail].vis[i];}}
}void dfs(int x){for(int i = head[x]; ~i; i = nex[i]){int y = ver[i];dfs(y);sizes[x] += sizes[y];}
}int ac_query(char s[]){int p = 0, ans = 0;int len = strlen(s);for(int i = 0;i < len;++i){//!计算有多少个单词在文本中出现p = ac[p].vis[s[i]-'a'];//往下一层走for(int k = p;k&&ac[k].ed!=-1;k = ac[k].fail){ans += ac[k].ed;ac[k].ed = -1;}}p = 0;for(int i = 0; i < len; ++ i){p = ac[p].vis[s[i] - 'a'];sizes[p] ++ ;//!记录匹配次数}for(int i = 1; i <= tot; ++ i)//!建fail树add(ac[i].fail, i);dfs(0);//!统计子树和for(int i = 1;i <= n; ++ i)printf("%d\n", sizes[match[i]]);return ans;
}char s[S];int main(){scanf("%d", &n);memset(head, -1, sizeof head);for(int i = 1;i <= n; ++ i){scanf("%s", s);insert(i, s);}//cout << "ok" << num ++  << endl;get_fail();//cout << "ok" << num ++  << endl;scanf("%s", s);//cout << "ok" << num ++  << endl;int res = ac_query(s);//cout << res << endl;return 0;
}

ac自动机fail树上dfs序建可持久化线段树

慕名而来

给定 n 个字符串 s1…ns_{1 \dots n}s1…n​。q 次询问 sks_ksk​在 sl…rs_{l \dots r}sl…r​中出现了多少次。 n,∑∣s∣≤2×105n,\sum |s| \le 2 \times 10^5n,∑∣s∣≤2×105q≤5×105q \le 5 \times 10^5q≤5×105

#include<bits/stdc++.h>
#define pb push_back
using namespace std;const int N=2e5+10,M=N*40;
int n,Q;namespace IO
{int read(){int ret=0;char c=getchar();while(!isdigit(c)) c=getchar();while(isdigit(c)) ret=ret*10+(c^48),c=getchar();return ret;}void write(int x){if(x>9)write(x/10);putchar(x%10^48);}void writeln(int x){write(x);putchar('\n');}
}
using namespace IO;namespace Tree
{vector<int>e[N];int ind,rt[N],st[N],en[N];struct Segment{int sz,ls[M],rs[M],sum[M];void copy(int x,int y){ls[x]=ls[y];rs[x]=rs[y];sum[x]=sum[y];}void pushup(int x){sum[x]=sum[ls[x]]+sum[rs[x]];}void update(int y,int &x,int l,int r,int p){//printf("%d %d %d %d %d\n",y,x,l,r,p);x=++sz;copy(x,y);if(l==r){sum[x]++;return;}int mid=(l+r)>>1;if(p<=mid) update(ls[y],ls[x],l,mid,p);else update(rs[y],rs[x],mid+1,r,p);pushup(x);//printf("%d %d %d\n",l,r,sum[x]);}int query(int y,int x,int l,int r,int L,int R){if(L<=l && r<=R) return sum[x]-sum[y];int mid=(l+r)>>1,res=0;if(L<=mid) res+=query(ls[y],ls[x],l,mid,L,R);if(R>mid) res+=query(rs[y],rs[x],mid+1,r,L,R);return res;}}tr;void dfs(int x){st[x]=++ind;for(int i=0;i<(int)e[x].size();++i) dfs(e[x][i]);en[x]=ind;}
}
using namespace Tree;namespace ACM
{int ed[N],len[N];char s[N];vector<int>ts[N];queue<int>q;struct ACM{int sz,rt,fail[N],ch[N][26];void init(){rt=sz=1;}void in(int id){scanf("%s",s+1);len[id]=strlen(s+1);int now=rt;//printf("%d %d\n",id,len[id]);for(int i=1;i<=len[id];++i){ts[id].pb(s[i]-'a');int x=s[i]-'a';if(!ch[now][x]) ch[now][x]=++sz;now=ch[now][x];}ed[id]=now;}void getfail(){q.push(rt);while(!q.empty()){int x=q.front();q.pop();for(int i=0;i<26;++i){if(!ch[x][i]) continue;int t=fail[x],t1=ch[x][i];while(t && !ch[t][i]) t=fail[t];fail[t1]=t?ch[t][i]:rt;q.push(t1);}}for(int i=1;i<=sz;++i) e[fail[i]].pb(i);}}S;
}
using namespace ACM;void build(int y,int &x,int id)
{int now=S.rt,las=y;//printf("%d %d %d %d\n",id,len[id],st[ed[id]],en[ed[id]]);for(int i=0;i<len[id];++i){now=S.ch[now][ts[id][i]];tr.update(las,x,1,ind,st[now]);las=x;}
}int main()
{#ifndef ONLINE_JUDGEfreopen("CF547E.in","r",stdin);freopen("CF547E.out","w",stdout);
#endifn=read();Q=read();S.init();for(int i=1;i<=n;++i) S.in(i);S.getfail();dfs(S.rt);for(int i=1;i<=n;++i) build(rt[i-1],rt[i],i);while(Q--){int l=read(),r=read(),id=read();printf("%d\n",tr.query(rt[l-1],rt[r],1,ind,st[ed[id]],en[ed[id]]));}return 0;
}

模板 - AC自动机相关推荐

  1. HDU 2222 ACAM模板(AC自动机)

    这里找到了两篇很nice的Trie树(作者Hackbuteer1)以及AC自动机(作者niushuai666)入门详解.博主写的可以说是非常用心了,一看就懂. 题意:给出N(<=10000)个单 ...

  2. 提高篇 第二部分 字符串算法 第4章 AC自动机

    https://blog.csdn.net/wangyh1008/article/details/81428056 [模板]AC自动机(加强版) 洛谷3796 AC自动机_A_loud_name-CS ...

  3. ac自动机 匹配最长前缀_AC自动机算法

    AC自动机简介: 首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一.一个常见的例子就是给出n个单词,再给出一段包 ...

  4. AC自动机算法及模板

    AC自动机算法及模板 2016-05-08 18:58 226人阅读 评论(0) 收藏 举报  分类: AC自动机(1)  版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 关于 ...

  5. P5357 【模板】AC自动机(二次加强版)(AC自动机建fail树dfs求模式串出现次数)

    P5357 [模板]AC自动机(二次加强版)(AC自动机建fail树dfs求模式串出现次数) 传送门 形式上,AC 自动机基于由若干模式串构成的 Trie 树,并在此之上增加了一些 fail 边:本质 ...

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

    二次联通门 : luogu P3808 [模板]AC自动机(简单版) /*luogu P3808 [模板]AC自动机(简单版)手速越来越快了10分钟一个AC自动机一遍过编译 + 一边AC感觉不错我也就 ...

  7. HDU2222 Keywords Search(AC自动机模板)

    AC自动机是一种多模式匹配的算法.大概过程如下: 首先所有模式串构造一棵Trie树,Trie树上的每个非根结点都代表一个从根出发到该点路径的字符串. 然后每个结点都计算出其fail指针的值,这个fai ...

  8. 数单词 (AC自动机模板题)

    数单词 时间限制:1000 ms  |  内存限制:65535 KB 难度:4 描述 为了能够顺利通过英语四六级考试,现在大家每天早上都会早起读英语. LYH本来以为自己在6月份的考试中可以通过六级, ...

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

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

最新文章

  1. 深入探秘 Netty、Kafka 中的零拷贝技术!
  2. 16个matplotlib绘图实用小技巧!
  3. SOJ 8064 Whack the Groundhog
  4. 非985/211学校的毕业生,进大厂的机率有多大?
  5. [转]网络性能评估工具Iperf详解(可测丢包率)
  6. C语言strcmp函数用法
  7. jQuery API中文手册详解
  8. 信息系统集成监理费收取标准_信息系统工程监理与咨询服务收费参考标准起草说明...
  9. 《游戏设计艺术(第2版)》——学习笔记(13)第13章 游戏机制必须平衡
  10. linux输入文件后clustalw,合并提取后的domain序列之后,linux系统中的clustalw不能读出蛋白信息...
  11. 项目配置文件----.eslintignore,eslint在做风格检查的时候忽略 dist 和 vender(第三方库) 不去检查。
  12. 【DBA100人】胡中豪:国产分布式数据库DBA炼成记
  13. ionic5中轮播图ion-slides、ion-slide的使用
  14. Failed to declare queue(s):[XXX]问题解决
  15. SVN设置忽略文件夹
  16. 求余小技巧 码农场 » POJ 3641 Pseudoprime numbers 题解 《挑战程序设计竞赛》
  17. 递归实现全排列(python)
  18. VUI-百度语音之Rest接口python学习笔记0
  19. 计算机文化基础(高职高专版 第十一版)第四章答案
  20. 一维搜索算法介绍及其实现

热门文章

  1. 基于点云的3D障碍物检测
  2. 你了解如何评估模型吗?
  3. 理解什么是MyBatis?
  4. 分布式事物-2pc和3pc区别
  5. Linux(Windows)下如何改变网卡的LinkSpeed工作模式
  6. Android项目Tab类型主界面大总结 Fragment+TabPageIndicator+ViewPager
  7. SQL基本点—— 思维导图
  8. 前端那些事之Bootstrap篇
  9. 驰骋工作流程引擎案例-水质检测工作流程设计开发实现过程
  10. 文档相似度的比较tf-idf lda lsi