字符串 ac自动姬

前言

省选临近,不能再颓了! 说着开始研究起moonlight串流。真香

本期博客之所以在csdn上发了一份,因为没有图床!如果有图床我一定会自力更生的!

好像和字符串没有毛关系

总之,为了备考省选,特地温习了一下ac自动姬

介绍

ac自动姬是一种多模匹配算法。说的直白一点,就是kmp的升级版,同时进行多个kmp。

说是多个kmp,其实它更多的借鉴的是kmp的思想,而不是算法。不会kmp可能可以理解ac自动姬,就像是不会加法也有可能理解乘法(多个加法);但是像线段树和树链剖分就有着严格的先后顺序,不可能在没有掌握线段树时就能理解树链剖分。

  1. trie树为必备知识,不懂trie树的童鞋请先学习trie树!(●ˇ∀ˇ●)
  2. kmp为非必备,学习完kmp再学习ac自动姬会更加深刻的理解

理论中的理论部分

对于多模匹配,我们肯定需要将模式串存储下来。怎么存?trie树呗

我们以一下模式串作为例子:

test

testt

est

好了,用trie树,我们把所有的模式串存了下来。

存完模式串,我们就开始匹配。随着主串的不断匹配,我们可以将匹配结果归纳如下:

  1. 假如当前匹配节点存在通向下一个节点的边,那么就转移

  2. 否则就是找不到下一个节点,那么就要按照失配来处理

这时就需要引入一种概念,“失配指针”。

什么是失配指针?失配指针就是当前点失配后,转移到的另外一个节点。(不太好理解,接着看下去)参照kmp理解一下失配指针

对象 kmp ac自动姬
失配指针 最长公共前后缀的坐标 trie树深度最深的合法坐标

还是不好理解?我们先把上述例子中的失配指针构建出来

照着图接着分析分析

参照kmp单模匹配。在kmp中,相当于只有左侧(\(root \Rightarrow t \Rightarrow e \Rightarrow s \Rightarrow t\))的这一条链。我们不断的找“最长公共前后缀”,是为了最大化利用我们已经匹配过的部分。

我们之所以要找“公共前后缀”,就是为了使得下一次匹配的对象是合法的。后缀与前缀重合,说明这段后缀可能作为下一个匹配的前缀被利用

而我们之所以要找“最长”的那一个,就是为了最大化的利用这个信息。假如当前公共前后缀存在不同的两个,且\(l_1<l_2\)。那么\(l_1\)长度的串一定包含在\(l_2\)长度的串中。如果\(l_2\)再次失配,它可以跳到\(l_1\)上,但是\(l_1\)失配,它却无法跳到\(l_2\)上

提供样例:ababa

“aba”与“a”分别是两个公共前后缀

“a”包含在“aba”中

好了,情况扩展到了ac自动姬多模匹配上。在多模匹配上就相当于提供了更多的可能来实现“最长公共前后缀”,一定要在保证“公共前后缀”的基础上尽可能“最长”,这样才能不浪费已经匹配出来的信息

理论中的代码部分

好了,基础的理论有了,我们就要开始实现。

回顾算法,主要难点分为两部分:构建fail指针匹配

构建fail指针

显然,我们在构建一个某一个节点的fail指针时,一定要事先求出来了所有深度小于这个节点的fail指针。换言之所有的fail只和深度小于这个点的节点有关联。

所有深度一致的先搜出来,保证深度按递增顺序搜出来...BFS!! 不难发现BFS就可以完美满足所有上述要求

我们取奔波最远的一个指针作为例子

在这个例子中,紫色的c为了获得fail指针,一共移动了两次

第一次:从c的父节点的fail指针开始寻找,发现第一个"b"下面并没有"c",接着转移

第二次:转移到了第二个b,发现这次,b下面接了一个c,那么就为这个c找到了失配指针

否则,假如到头了仍然没有找到,就将fail指针设为root即可

代码:

void getfail(){
//单独处理根节点
fail[root]=-1;
queue <int> line; line.push(root);
while(!line.empty()){
int u=line.front(); line.pop();
for(int i=0;i<=25;++i){
if(ch[u][i]){ //假如u节点后面跟着i节点
line.push(ch[u][i]);
int tmp=fail[u];
//如果一下就到头了
if(tmp==-1){
fail[ch[u][i]]=root;
}
//否则尝试匹配
else{
while(tmp!=root&&!ch[tmp][i]{
tmp=fail[tmp];
}
if(tmp!=root||ch[tmp][i]){
fail[ch[u][i]]=ch[tmp][i];
}
else{
fail[ch[u][i]]=root;
last[ch[u][i]]=root;
}
}
}
}
}
}

匹配

好了,fail指针也构建完了,就可以开始匹配了

  1. 假如当前匹配节点存在通向下一个节点的边,那么就转移

  2. 否则就是找不到下一个节点,那么就要按照失配来处理

(搬过来~~)

我们知道怎么转移之后还有一个重要的问题:怎么统计?

从一个节点,通过fail指针遍历到的字串一定是该串的一个后缀,而鬼知道哪一个字串会被模式串匹配上,所以统计一定是随匹配转移时刻进行的。

而且,fail指针的转移的过程中,一定会遍历到所有被当前串包含的公共前后缀。因此可以保证正确性。

由于建trie树的时候记录了以某一点结尾的权值,故沿途加上所有权值即可。

代码如下:

void kmp(string a){
//转移部分
int len=a.size();
int u=root;
for(int i=0;i<len;++i){
int v=a[i]-'a';
while(u!=root&&!ch[u][v]){
u=fail[u];
}
if(u!=root||ch[u][v]) u=ch[u][v];
else u=root; //统计部分
int tmp=u;
while(tmp!=root){
rec[type[tmp]]+=dot[tmp];
tmp=last[tmp];
}
}
}

last优化

在转移过程中,会遇到很多权值为0(模式串中不包含),但fail指针却跳到了,导致fail指针要大量跳过一些对答案没有用的节点。

last优化就是要想法设法避免这件事。由于权值为0的fail指针是毫无意义的,那么就设last指针必定跳向一个权值不为0的指针

假如(fail指针所指向的点,其权值大于零)
last指针就是fail指针
否则
last指针就是fail指针指向的点的last指针

简单的实现:

last[u]=dot[fail[u]]?fail[u]:last[fail[u]];

总结

至此,ac自动姬已经从理论到代码都有了完整的实现一举,参考代码如下:

该代码记录的是

struct trie{
static const int MAX=3e5+6;
int cnt,tot,root;
int ch[MAX][26],fail[MAX],last[MAX],dot[MAX]; void clean(){
cnt=0; tot=0; root=0;
memset(ch,0,sizeof(ch));
memset(fail,0,sizeof(fail));
memset(last,0,sizeof(last));
memset(dot,0,sizeof(dot));
} void insert(string a){
tot++;
int len=a.size();
int u=root;
for(int i=0;i<len;++i){
int v=a[i]-'a';
if(!ch[u][v]){
ch[u][v]=++cnt;
}
u=ch[u][v];
}
dot[u]++; type[u]=tot;
} void getfail(){
fail[root]=-1; last[root]=-1;
queue <int> line; line.push(root);
while(!line.empty()){
int u=line.front(); line.pop();
for(int i=0;i<=25;++i){
if(ch[u][i]){
line.push(ch[u][i]);
int tmp=fail[u];
if(tmp==-1){
fail[ch[u][i]]=root;
last[ch[u][i]]=root;
}
else{
while(tmp!=root&&!ch[tmp][i]){
tmp=fail[tmp];
}
if(tmp!=root||ch[tmp][i]){
fail[ch[u][i]]=ch[tmp][i];
last[ch[u][i]]=dot[fail[ch[u][i]]]?fail[ch[u][i]]:last[fail[ch[u][i]]];
}
else{
fail[ch[u][i]]=root;
last[ch[u][i]]=root;
}
}
}
}
}
} int kmp(string a){
int len=a.size(),ans=0;
int u=root;
for(int i=0;i<len;++i){
int v=a[i]-'a';
while(u!=root&&!ch[u][v]){
u=fail[u];
}
if(u!=root||ch[u][v]) u=ch[u][v];
else u=root; int tmp=u;
while(tmp!=root){
ans+=dot[tmp]; dot[tmp]=0;
tmp=last[tmp];
}
}
return ans;
} void print(int u){
printf("%d %d\n",u,fail[u]);
for(int i=0;i<=25;++i){
if(ch[u][i]) print(ch[u][i]);
}
}
}tree;

后记

深夜里又打了6e3个字..(>人<;)

希望通过这篇博客,能为自己和所有看到的人提供一点思路(´▽`ʃ♡ƪ)

ac自动姬的更多相关文章

  1. C#利用POST实现杭电oj的AC自动机器人,AC率高达50%~~

    暑假集训虽然很快乐,偶尔也会比较枯燥,,这个时候就需要自娱自乐... 然后看hdu的排行榜发现,除了一些是虚拟测评机的账号以外,有几个都是AC自动机器人 然后发现有一位作者是用网页填表然后按钮模拟,, ...

  2. URAL 1158 AC自动机上的简单DP+大数

    题目大意 在一种语言中的字母表中有N(N<=50)个字母,每个单词都由M(M<=50)个字母构成,因此,一共可以形成N^M个单词.但是有P(P<=10)个串是被禁止的,也就是说,任何 ...

  3. hdu 3247 AC自动+状压dp+bfs处理

    Resource Archiver Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 100000/100000 K (Java/Ot ...

  4. hdu 2243 考研路茫茫——单词情结(AC自动+矩阵)

    考研路茫茫——单词情结 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  5. 【洛谷4045】[JSOI2009] 密码(状压+AC自动机上DP)

    点此看题面 大致题意: 给你\(n\)个字符串,问你有多少个长度为\(L\)的字符串,使得这些字符串都是它的子串.若个数不大于\(42\),按字典序输出所有方案. 状压 显然,由于\(n\)很小,我们 ...

  6. POJ 3691 AC自动机上的dp

    题目大意: 给定一些不合理的DNA序列,再给一段较长的dna序列,问最少修改几次可以使序列中不存在任何不合理序列,不能找到修改方法输出-1 这里你修改某一个点的DNA可能会影响后面,我们不能单纯的找匹 ...

  7. HNU 13108-Just Another Knapsack Problem (ac自动机上的dp)

    题意: 给你一个母串,多个模式串及其价值,求用模式串拼接成母串(不重叠不遗漏),能获得的最大价值. 分析: ac自动机中,在字典树上查找时,用dp,dp[i]拼成母串以i为结尾的子串,获得的最大价值, ...

  8. POJ 1204 Word Puzzles | AC 自动鸡

    题目: 给一个字母矩阵和几个模式串,矩阵中的字符串可以有8个方向 输出每个模式串开头在矩阵中出现的坐标和这个串的方向 题解: 我们可以把模式串搞成AC自动机,然后枚举矩阵最外围一层的每个字母,向八个方 ...

  9. bzoj [Sdoi2014]数数 AC自动机上dp

    [Sdoi2014]数数 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 1264  Solved: 636[Submit][Status][Discu ...

  10. 【BZOJ1030】[JSOI2007] 文本生成器(AC自动机上跑DP)

    点此看题面 大致题意: 给你\(N\)个字符串(只含大写字母),要你求出有多少个由\(M\)个大写字母构成的字符串含有这\(N\)个字符串中的至少一个. \(AC\)自动机 看到题目,应该比较容易想到 ...

随机推荐

  1. 40.TokenAuthentication认证

    TokenAuthentication认证介绍 TokenAuthentication是一种简单的基于令牌的HTTP认证 适用于CS架构,例如普通的桌面应用程序或移动客户端   TokenAuthen ...

  2. 某 .NET RabbitMQ SDK 有采集行为,你怎么看?

    一:背景 1.讲故事 前几天有位朋友在微信上找到我,说他的一个程序上了生产之后,被运维监控定位到这个程序会向一个网址为: http://m.365ey.net 上不定期打数据,而且还是加密的格式,要他 ...

  3. 深度剖析Java的volatile实现原理,再也不怕面试官问了

    上篇文章我们讲了synchronized的用法和实现原理,我们总爱说synchronized是重量级锁,volatile是轻量级锁.为什么volatile是轻量级锁,体现在哪些方面?以及volatil ...

  4. 关系抽取--Relation Extraction: Perspective from Convolutional Neural Networks

    一种使用CNN来提取特征的模型,通过CNN的filter的大小来获得不同的n-gram的信息,模型的结构如下所示: 输入 输入使用word2vec的50维词向量,加上 position embeddi ...

  5. 还在用双层for循环吗?太慢了

    前情提要 我们在开发中经常碰到这样的场景,查出两个 list 集合数据,需要根据他们相同的某个属性为连接点,进行聚合.但是平时我们使用的时候关注过性能吗?下面让我们一起来看看它的表现如何. 来个例子 ...

  6. JVM学习笔记——垃圾回收篇

    JVM学习笔记--垃圾回收篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的垃圾回收部分 我们会分为以下几部分进行介绍: 判断垃圾回收对象 垃圾回收算法 分代垃圾回收 垃圾回收器 ...

  7. 微信DAT文件解密(dat转图像)

    微信电脑版现在已经是日常工作生活必不可少的工具,有时候删除了聊天记录或者被系统清理软件清理了,但还想查看曾经的微信聊天图片. 这个时候辛辛苦苦找到了文件,却发现无法查看,因为微信电脑版为了保护我们的隐 ...

  8. [CS61A] Lecture 5&6&7. Environments & Design & Functions Examples & Homework 2: Higher Order Functions

    [CS61A] Lecture 5&6&7. Environments & Design & Functions Examples & Homework 2: ...

  9. 真正“搞”懂HTTP协议04之搞起来

    前两篇文章,我们从空间和时间的角度都对HTTP有了一定的学习和理解,那么基于上一篇的HTTP发展的时间顺序,我会在后面的文章由浅入深,按照HTTP版本内容的更迭,一边介绍相关字段的使用方法,一边讲解其 ...

  10. ArrayList 可以完全替代数组吗?

    本文已收录到  GitHub · AndroidFamily,有 Android 进阶知识体系,欢迎 Star.技术和职场问题,请关注公众号 [彭旭锐] 加入 Android 交流群. 前言 大家好, ...