blog:www.wjyyy.top

    AC自动机是一种毒瘤的方便的多模式串匹配算法。基于字典树,用到了类似KMP的思维。

    AC自动机与KMP不同的是,AC自动机可以同时匹配多个模式串,而复杂度不会达到太高。如果用KMP多次匹配字符串,复杂度就是\(O(k(n+m))\)。

    我们知道,如果让一个字符串头对头或者完全匹配其他字符串,用字典树来匹配是最为方便的。但是如果匹配过程中发现当前节点没有目标儿子,就发生了失配。在KMP字符串匹配中,失配可以跳到给当前位置预处理出的nxt,继续匹配。

    而AC自动机在字典树上,我们如何找出每个节点失配位置呢?我们知道,像KMP一样,失配位置是唯一确定的。而在字典树上,一条路径唯一对应了一个子串,因此也是唯一确定的。

    KMP中的nxt数组是由变量j承接了前一个位置的nxt,我们考虑在AC自动机中也让失配指针从父节点转移过来。那么如此一来,当前节点(设为'c')的失配指针就会从当前父节点的失配指针一直沿失配指针递归,找到第一个有以'c'字符为儿子的节点,把当前节点的失配指针连接到这个节点的'c'儿子上。如此做下去,会发现有了失配指针的树变成了一个图。但是如果上面的回溯过程找到根了还没有找到怎么办?

    正常情况下,字典树的根是不带任何字符的,也就是说它是一个空节点,也是重新匹配的开始。如果我们一开始匹配就出现了失误,也就是根节点都没有这个儿子,我们当然要留在自己位置上继续做,因此根节点的失配指针指向自己(同时防止越界)。同理,根节点的儿子们失配指针指向根节点,因为在这里失配了,接下来只有两种情况:一是根节点也没有这个儿子,于是回归到根节点的一般情况;二是根节点有这个儿子,根节点有这个儿子我们就通过当前节点的失配指针先走到根节点,再走到这个儿子去。

    于是,我们的Trie树变成了Trie图。

    可以根据上面的前两幅图更清楚的了解AC自动机,其中红色边是fail边。我们可以发现一个有趣的事情,fail指针可以构成一棵有向树,注意到每条单独的链都没有分支,而且一条链上的字母总是一致的,因此可能在以后的题目或者优化中出现。(就像KMP的nxt一样)

    实际上在构建AC自动机时,我们的失配指针并不这样建。为了减小常数(也许是这个原因),我们认为当前节点如果没有儿子'c',就把当前节点代表'c'儿子的指针连向当前节点失配指针的'c'儿子。因为一个点的失配指针指向的节点总是比这个点浅,所以我们用BFS来做,深度较浅的点总比深度深的点先被访问,也因此,当前节点的失配指针的'c'儿子一定有位置,即使不是它真正的儿子,也一定是它通过失配指针索引得到的。在最坏的情况下,如果失配指针回溯的过程中怎么也找不到这样的儿子,自然而然当前节点的'c'儿子就连向根了。

    与字典树类似,AC自动机成功匹配就是找到了一个单词的结尾,我们在构建字典树时就应该把每个模式串的结尾做上标记。但是如果两个模式串有包含关系怎么办?有两种方法可以完成,一是访问到每个节点时暴力跳fail指针,直到递归到根,对答案的贡献就是这条路径的标记数;二是构建fail树,跳就是沿着fail树在跳,只需要预处理出fail树上每个节点到根路径上标记的数目(前缀和),就可以在当前节点记录答案。看上去第二种方法复杂度更优,但是它有局限性。也就是当确切地统计每个模式串出现的次数时,这种直接用fail树统计出现次数和的方法不能适用。

Code of luoguP3796:

    这个题要注意重复的模式串统计问题

#include<cstdio>
#include<cstring>
#include<vector>
using std::vector;
vector<int> same[155];//与某一个模式串相同的模式串编号
struct node
{int End,num;//num表示相同模式串个数,End表示是否为结束位置node *ch[26];node *fail;node(){memset(ch,0,sizeof(ch));fail=NULL;End=0;num=0;}void build(char *c,int i)//构建字典树{if(*c=='\0'){End=1;if(!num)num=i;same[num].push_back(i);//如果发现这里已经有单词结束了,那么一定是重复的,直接向原来的后面加编号就好了return;}if(!ch[*c-'a'])ch[*c-'a']=new node();ch[*c-'a']->build(c+1,i);}
}*root=new node();
char t[200][200];
node *q[1000011];//用队列完成BFS
int l=0,r=0;
void Fail()//构建fail指针
{root->fail=root;//没有这句话貌似也可以,为了保险起见,防止越界for(int i=0;i<26;++i)//根节点的儿子失配指针都指向自己if(!root->ch[i])//没有这个儿子就指向失配指针的这个儿子,而失配指针是自己,为了不紊乱和方便,这个儿子就指向自己root->ch[i]=root;else{root->ch[i]->fail=root;//设置失配指针q[++r]=root->ch[i];}while(l<r){node *p=q[++l];for(int i=0;i<26;++i)if(p->ch[i]){p->ch[i]->fail=p->fail->ch[i];//有这个儿子就设置失配指针到自己的失配指针,自己的失配指针指向的地方一定已经完成工作了q[++r]=p->ch[i];}elsep->ch[i]=p->fail->ch[i];}return;
}
char s[1000010];
int cnt[155];
void match()
{int ans=0;scanf("%s",s);node *now=root;for(int i=0;s[i]!='\0';++i)//开始匹配{now=now->ch[s[i]-'a'];cnt[now->num]+=now->End;node *p=now;while(p!=root)//暴力跳fail{p=p->fail;cnt[p->num]+=p->End;}}
}
int main()
{int n;scanf("%d",&n);while(n){root=new node();memset(cnt,0,sizeof(cnt));for(int i=1;i<=n;++i){scanf("%s",t[i]);root->build(t[i],i);}Fail();match();int mx=0;for(int i=1;i<=n;++i){for(vector<int>::iterator it=same[i].begin();it!=same[i].end();++it)//处理相同模式串cnt[*it]=cnt[i];if(mx<cnt[i]){cnt[0]=1;mx=cnt[i];}else if(mx==cnt[i])++cnt[0];}printf("%d\n",mx);for(int i=1;i<=n;++i)if(cnt[i]==mx)printf("%s\n",t[i]);scanf("%d",&n);}return 0;
}

转载于:https://www.cnblogs.com/wjyyy/p/ACautomaton.html

【AC自动机】【字符串】【字典树】AC自动机 学习笔记相关推荐

  1. 字典树基础知识学习笔记

    节点最多有N个时,开一个二维数组son[N][M](M为所有字符的总个数),记录每个点的儿子.对每一个字符串的结尾的序号,用cnt[N] 数组来记录有多少个这样的字符串,这张图可以帮助理解: 假设给定 ...

  2. HDOJ 1251 统计难题——第二次用字典树AC题目,写一下解题报告

    第一次用字典树+BFS的方法统计以给定字符串为前缀的单词数目,超时了.百思不得其解,然后我看了一下讨论版里的一位同学的AC代码.豁然开朗,立刻明白了求解相同前缀的单词个数的最简单的解法. 其实在建一科 ...

  3. 字典树实现_学习NLP的第3天——字典树

    通过<自然语言处理入门>(何晗)的第2章来学习一下分词的常用算法,因此以下的实现方法都是通过HanLP实现的.这里主要记录我在学习过程中整理的知识.调试的代码和心得理解,以供其他学习的朋友 ...

  4. 深信服上网行为管理(AC)、安全网关(SG)学习笔记

    深信服上网行为管理的学习笔记,由于AC软件版本更新相关特性可能变动,仅供参考哈.. 权威内容请访问深信服官方社区:https://bbs.sangfor.com.cn/ 目录 默认IP 接口保留地址 ...

  5. 字典树 [字典树相关扩展与应用字典树AC自动机] _ CodingPark编程公园

    基础知识 print()语句中的end=" "的含义 Print不换行:end传递一个空字符串,这样print函数不会在字符串末尾添加一个换行符,而是添加一个空字符串 doctes ...

  6. NYOJ 685 查找字符串 字典树

    查找字符串 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描述 小明得到了一张写有奇怪字符串的纸,他想知道一些字符串出现了多少次,但这些字符串太多了,他想找你帮忙,你能帮他吗 ...

  7. 王树森强化学习笔记——多智能体强化学习

    多智能体强化学习 想要了解更多强化学习的内容,推荐观看王树森教授的教学视频 深度强化学习(王树森) 设定 在之前的学习当中,我们讨论的都是单个智能体如何进行决策,然而现实中还存在需要同时控制多个智能体 ...

  8. python字典实现原理_python学习笔记_第7天(字典底层原理+选择结构)

    字典:(拓展–重要)字典核心底层原理 字典对象的核心是散列表,散列表是一个稀疏数组(总是有空白元素的数组),数组的每个单元叫做bucket. 每个bucket 有两部分:一个是键对象的引用,一个是值对 ...

  9. php字符串加加运算,php 学习笔记

    一.了解php 1.什么是php PHP 超文本预处理器:服务器端的脚本语言:是一种被广泛应用的开放源代码的多用途脚本语言:他可以嵌入到html中:尤其适用web开发. 2.php在web中的应用 服 ...

  10. 表达式树 php,Linux_LINQ学习笔记:表达式树,构建查询表达式 本节中, 我们 - phpStudy...

    构建查询表达式 本节中, 我们假设我们拥有一个这样的实体类: 1: [Table] public partial class Product 2: 3: { 4: 5: [Column(IsPrima ...

最新文章

  1. 运维调试记录:Opendaylight铍版本开发环境搭建流程
  2. leetcode之Climbing Stairs爬楼梯
  3. 用Python实现反转字符串
  4. 脚本命令配置mysql_MySQL常用的配置、脚本和命令
  5. jdk下没有java源码_openJDK之如何下载各个版本的openJDK源码
  6. c语言宏定义#define的理解与资料整理
  7. python 电路仿真spice_电路仿真SPICE入门
  8. 技嘉x58不支持服务器内存,一般机箱放不下 技嘉X58送海盗船内存
  9. 近年来的Java面试题汇总。帮你圆大厂梦。
  10. vuforia 模型识别_汽车的优势:Vuforia模型目标
  11. 旅游类App原型制作分享-Triposo
  12. web漏洞扫描器原理_【技术分享】漏洞扫描技巧篇Web 漏洞扫描器
  13. 【计算几何】计算几何复习
  14. 关于正确处理0x80070426等错误的方法
  15. 手机怎么解决同ip多账号_抖音播放量低怎么办?如何提高抖音播放量上热门?...
  16. 51单片机c语言的秒表设计,基于51单片机的计时器设计
  17. Excel:RAND随机类函数
  18. 【易语言模块】MP3信息标签解析V1.0[源码]分享
  19. GEO数据库数据下载方法总结
  20. vivox21i的Android版本号,vivo X21i系统版本是多少?

热门文章

  1. Java实现 简单聊天软件
  2. CSS3 filter:drop-shadow滤镜与box-shadow区别
  3. vue-cli#2.0项目结构分析
  4. linux 批量kill进程
  5. Unable to instantiate default tuplizer [org.hibernate.tuple.entity.PojoEntityTuplizer]
  6. DNS(二)--正反解析及主从配置
  7. Exchange2003-2010迁移系列之六,配置及配置第二台Exchange CAS/HUB服务器
  8. springmvc原理详解(手写springmvc)
  9. JAVA8的LocalDateTime使用心得和工具类
  10. Xamarin Essentials教程磁力计Magnetometer