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];
}
else
p->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;
}

【AC自动机】【字符串】【字典树】AC自动机 学习笔记的更多相关文章

  1. 字典树(Trie)的学习笔记

    按照一本通往下学,学到吐血了... 例题1 字典树模板题吗. 先讲讲字典树: 给出代码(太简单了...)! #include<cstdio> #include<cstring> ...

  2. 字典树&&AC自动机---看完大概应该懂了吧。。。。

    目录 字典树 AC自动机 字典树 又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计 ...

  3. 珂朵莉树(Chtholly Tree)学习笔记

    珂朵莉树(Chtholly Tree)学习笔记 珂朵莉树原理 其原理在于运用一颗树(set,treap,splay......)其中要求所有元素有序,并且支持基本的操作(删除,添加,查找......) ...

  4. 字典树trie的学习与练习题

    博客详解: http://www.cnblogs.com/huangxincheng/archive/2012/11/25/2788268.html http://eriol.iteye.com/bl ...

  5. Object-C 语法 字符串 数组 字典 和常用函数 学习笔记

    字符串 //取子字符串 NSString *str1=@"今天的猪肉真贵,200块一斤"; NSString *sub1=[str1 substringFromIndex:4]; ...

  6. 树链剖分学习笔记 By cellur925

    先%一发机房各路祖传树剖大师%%%. 近来总有人向我安利树剖求LCA,然鹅我还是最爱树上倍增.然鹅又发现近年一些题目(如天天爱跑步.运输计划等在树上进行操作的题目),我有把树转化为一条链求解的思路,但 ...

  7. JavaScript字符串常用操作函数之学习笔记

    字符串简介 使用英文单引号或双引号括起来,如:’Hello’,”World”,但是不能首尾的单引号和双引号必须一致,交错使用,如果要打印单引号或者双引号,可以使用转义字符\’(单引号),\”(双引号) ...

  8. Unity3D行为树插件Behave学习笔记

    Behave1.4行为树插件 下载地址:http://pan.baidu.com/s/1i4uuX0L 安装插件和使用 我们先来看看插件的安装和基本使用方法,新建一个Unity3D项目,这里我使用的是 ...

  9. BZOJ 2595: [Wc2008]游览计划 [DP 状压 斯坦纳树 spfa]【学习笔记】

    传送门 题意:略 论文 <SPFA算法的优化及应用> http://www.cnblogs.com/lazycal/p/bzoj-2595.html 本题的核心就是求斯坦纳树: Stein ...

  10. 浅谈树套树(线段树套平衡树)&学习笔记

    0XFF 前言 *如果本文有不好的地方,请在下方评论区提出,Qiuly感激不尽! 0X1F 这个东西有啥用? 树套树------线段树套平衡树,可以用于解决待修改区间\(K\)大的问题,当然也可以用 ...

随机推荐

  1. powerdesigner设计的pdm模型导出清晰图片格式

    用powerdesigner设计了数据库模型,想把模型粘贴到数据库文档中,之前一直是Ctrl+A然后复制,直接粘贴过去的,这次领导说放大看不清,o(╯□╰)o 没办法,得搞个高清图复制上去啊,怎么办呢 ...

  2. C++——多线程

    1.多进程和多线程:进程是一个总任务,一个进程可能包含多个线程. 2.并行和并发: 并发的关键是你有处理多个任务的能力,不一定要同时. 并行的关键是你有同时处理多个任务的能力. 3.共享数据的管理和线 ...

  3. 2014蓝桥杯B组初赛试题《奇怪的分式》

    题目描述: 上小学的时候,小明经常自己发明新算法.一次,老师出的题目是:     1/4 乘以 8/5      小明居然把分子拼接在一起,分母拼接在一起,答案是:18/45 (参见图1.png)   ...

  4. lucene 第二天

    Lucene/Solr   第二天 1. 课程计划 Lucene的Field Lucene的索引库维护 lucene的查询 a) Query子对象 b) QueryParser Lucene相关度排序 ...

  5. Service和IntentService的区别

    不知道大家有没有和我一样,以前做项目或者练习的时候一直都是用Service来处理后台耗时操作,却很少注意到还有个IntentService,前段时间准备面试的时候看到了一篇关于IntentServic ...

  6. 数字图像处理实验(10):PROJECT 05-01 [Multiple Uses],Noise Generators 标签: 图像处理MATLAB 2017-05-26 23:36

    实验要求: Objective: To know how to generate noise images with different probability density functions ( ...

  7. Python3 BeautifulSoup和Pyquery解析库随笔

    BeautifuSoup和Pyquery解析库方法比较 1.对象初始化: BeautifySoup库: from bs4 import BeautifulSoup html = 'html strin ...

  8. 多线程协作 FileStream文件读写操作,读写冲突解决

    using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Tex ...

  9. 如果你的资源贫乏,那么专注做好一件事将是你的唯一出路(no reading yet)

    http://www.jianshu.com/p/8784f0fd7ab8/comments/1161511

  10. What’s the Difference Between a Value Provider and Model Binder?

    ASP.NET MVC 3 introduced the ability to bind an incoming JSON request to an action method parameter, ...