[算法模版]AC自动机

[算法模版]AC自动机

基础内容

板子不再赘述,OI-WIKI有详细讲解。

\(query\)函数则是遍历文本串的所有位置,在文本串的每个位置都沿着\(fail\)跳到根,将沿途所有元素答案++。意义在于累计所有以当前字符为结尾的所有模式串的答案。看代码就能很容易的理解。

另外\(e[i]\)记录的是第\(t\)个模式串结尾是哪个节点(所有节点均有唯一的编号)。

贴个P5357 【模板】AC自动机(二次加强版)板子:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#define maxn (int)(2e6+10000)
int ch[(int)(2e5+1000)][30],fail[maxn],cnt,e[maxn],nex[maxn],n,queue[maxn],ans[maxn];
using namespace std;
char s[(int)(2e6+1)];
char data[maxn];
void init() {
    memset(ch,0,sizeof(ch));
    memset(fail,0,sizeof(fail));
    memset(e,0,sizeof(e));
    memset(nex,0,sizeof(nex));
    memset(ans,0,sizeof(ans));
    cnt=0;
}
void insert(int t) {
    int now=0,len=strlen(s);
    for(int i=0;i<len;i++) {
        int num=s[i]-'a';
        if(!ch[now][num])ch[now][num]=++cnt;
        now=ch[now][num];
    }
    e[t]=now;
}

void build(){
    int l=0,r=0;
    for(int i=0;i<=26;i++)if(ch[0][i])queue[++r]=ch[0][i];
    while(l<r) {
        int now=queue[++l];
        for(int i=0;i<=26;i++) {
            if(ch[now][i]) {
                queue[++r]=ch[now][i];
                fail[ch[now][i]]=ch[fail[now]][i];
            }
            else ch[now][i]=ch[fail[now]][i];
        }
    }
}
void query() {
    int now=0;
    for(int i=0;data[i];i++) {
        now=ch[now][data[i]-'a'];
        for(int j=now;j;j=fail[j])ans[j]++;
    }
}
int main() {
    init();
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%s", s);
        insert(i);
    }
    build();
    scanf("%s", data);
    query();
    for(int i=1;i<=n;i++)printf("%d\n",ans[e[i]]);
    return 0;
}

然后你就会发现,你获得了TLE的好成绩。所以我们需要引入\(last\)优化。

last优化(引自sclbgw7)

博主懒,就不造轮子了。原文链接见参考文献。



上述方法将建图+匹配的复杂度成功优化为了 $??(∑??+??)O(∑n+m) $,但是别忘了,匹配成功时的计数也是需要跳fail边的。然而,为了跳到一个结束节点,我们可能需要中途跳到很多没用的伪结束节点:

如果一个节点的fail指向一个结尾节点,那么这个点也成为一个(伪)结尾节点。在匹配时,如果遇到结尾节点,就进行相应的计数处理。

这里面就又有优化的余地了:对于不是真正结束节点的伪结束点,直接跳过它就好了。我们用一个last指针表示“在它顶上的fail边所指向的一串节点中,第一个真正的结束节点”。于是,每次计数处理时,我们不跳fail边,改为跳last边,省去了很多冗余操作。

获得last指针的方法也十分简单,就是在void build()中加一句话:

last[c]=end[fail[c]]?fail[c]:last[fail[c]];

然后匹配时的代码就变成了:

void count(int x)
{
    while(x)
    {
       //计数、打印等,视题目要求顶
        x=last[x];
    }
}

void match()
{
    int now=1;
    for(int i=1;s[i]!='\0';++i)
    {
        int x=s[i]-'a';
        now=ch[now][x];
        if(end[now])count(now);
        else if(last[now])count(last[now]);
    }
}

注意:last优化是对复杂度没有影响的小优化,但是大多数情况下效果明显,类似于搜索剪枝。



使用last优化后AC的代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#define maxn (int)(2e6+10000)
int ch[(int)(2e5+1000)][30],fail[maxn],cnt,e[maxn],nex[maxn],n,queue[maxn],ans[maxn],last[maxn];
bool endf[maxn];
using namespace std;
char s[(int)(2e6+1)];
char data[maxn];
void init() {
    memset(ch,0,sizeof(ch));
    memset(fail,0,sizeof(fail));
    memset(e,0,sizeof(e));
    memset(nex,0,sizeof(nex));
    memset(ans,0,sizeof(ans));
    cnt=0;
}
void insert(int t) {
    int now=0,len=strlen(s);
    for(int i=0;i<len;i++) {
        int num=s[i]-'a';
        if(!ch[now][num])ch[now][num]=++cnt;
        now=ch[now][num];
    }
    e[t]=now;
    endf[now]=1;
}

void build(){
    int l=0,r=0;
    for(int i=0;i<=26;i++)if(ch[0][i])queue[++r]=ch[0][i];
    while(l<r) {
        int now=queue[++l];
        for(int i=0;i<=26;i++) {
            if(ch[now][i]) {
                queue[++r]=ch[now][i];
                if(endf[ch[fail[now]][i]]) {
                    last[ch[now][i]]=ch[fail[now]][i];
                }
                else last[ch[now][i]]=last[ch[fail[now]][i]];
                fail[ch[now][i]]=ch[fail[now]][i];
            }
            else ch[now][i]=ch[fail[now]][i];
        }
    }
}
void query() {
    int now=0;
    for(int i=0;data[i];i++) {
        now=ch[now][data[i]-'a'];
        for(int j=now;j;j=last[j])ans[j]++;
    }
}
int main() {
    init();
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%s", s);
        insert(i);
    }
    build();
    scanf("%s", data);
    query();
    for(int i=1;i<=n;i++)printf("%d\n",ans[e[i]]);
    return 0;
}

AC自动机+DP

咕咕咕

原文地址:https://www.cnblogs.com/GavinZheng/p/11407054.html

时间: 2024-11-10 01:09:59

[算法模版]AC自动机的相关文章

跳跃表,字典树(单词查找树,Trie树),后缀树,KMP算法,AC 自动机相关算法原理详细汇总

第一部分:跳跃表 本文将总结一种数据结构:跳跃表.前半部分跳跃表性质和操作的介绍直接摘自<让算法的效率跳起来--浅谈"跳跃表"的相关操作及其应用>上海市华东师范大学第二附属中学 魏冉.之后将附上跳跃表的源代码,以及本人对其的了解.难免有错误之处,希望指正,共同进步.谢谢. 跳跃表(Skip List)是1987年才诞生的一种崭新的数据结构,它在进行查找.插入.删除等操作时的期望时间复杂度均为O(logn),有着近乎替代平衡树的本领.而且最重要的一点,就是它的编程复杂度较同类

算法模板——AC自动机

实现功能——输入N,M,提供一个共计N个单词的词典,然后在最后输入的M个字符串中进行多串匹配(关于AC自动机算法,此处不再赘述,详见:Aho-Corasick 多模式匹配算法.AC自动机详解.考虑到有时候字典会相当稀疏,所以引入了chi和bro指针进行优化——其原理比较类似于邻接表,这个东西本身和next数组本质上是一致的,只是chi和bro用于遍历某一节点下的子节点,next用于查询某节点下是否有需要的子节点) 1 type 2 point=^node; 3 node=record 4 ex:

【算法】AC自动机/AC算法 - 多模式串快速匹配

AC自动机 Accepted Aho-Corasick 性质 AC自动机/AC算法(Aho-Corasick automaton),是著名的多模式串匹配算法. 前置知识 字典树(重要) KMP算法(了解Next数组的作用) 典例与算法复杂度分析 典型例题是:给定一个主串 S,给定多个模式串 T,问主串 S 中存在多少个给定的模式串 在KMP算法中,一个长度为n的主串一个长度为m的模式串的复杂度为 O(n+m) 而如果直接照搬KMP算法到这种题型下,模式串处理一次就需要匹配一次 如果有t个模式串,

杭电ACM1277——全文检索~~AC自动机算法

题目的意思:给你一篇文章,再给你T个字符串,判断这T个字符串有哪些在文章中出现过. 由于文章很大,普通的方法必定超时,所以需要用 AC自动机算法. AC自动机算法是多模匹配算法之一,主要是用于在一篇文章中,找出给定的N个单词在这篇文章中出现的个数. AC自动机算法,我也是刚刚学习,主要是在建立字典树的基础上,增加了失败指针,提高了匹配的效率.而且最难的是失败指针的建立. 它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高. 对于AC自动机算法,可以参考大神的博客:点击打开链接 里面有

AC自动机算法及模板

关于AC自动机 AC自动机:Aho-Corasickautomation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一.一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过.要搞懂AC自动机,先得有模式树(字典树)Trie和KMP模式匹配算法的基础知识.AC自动机算法分为3步:构造一棵Trie树,构造失败指针和模式匹配过程. 简单来说,AC自动机是用来进行多模式匹配(单个主串,多个模式串)的高效算法. AC自动机的构造过程 使用Aho-

数据结构与算法简记--多模式字符串匹配AC自动机

AC自动机 一样的不太好理解,有时间再啃 敏感词过滤 单模式字符串匹配算法:(BF,RK,BM,KMP)每次取敏感词字典中一个敏感语做为模式串在用户输入的主串中进行匹配,效率较低 多模式字符串匹配算法:(Trie树,AC自动机) Trie树:把用户输入的内容作为主串,从第一个字符(假设是字符 C)开始,在 Trie 树中匹配.当匹配到 Trie 树的叶子节点,或者中途遇到不匹配字符的时候,我们将主串的开始匹配位置后移一位,也就是从字符 C 的下一个字符开始,重新在 Trie 树中匹配. Trie

hdu 2222 Keywords Search(ac自动机入门题)

1 /************************************************************ 2 题目: Keywords Search(hdu 2222) 3 链接: http://acm.hdu.edu.cn/showproblem.php?pid=2222 4 算法: ac自动机 5 算法思想: 多个字符串匹配,也就是相当于多个kmp 6 ***********************************************************

hdu2222 Keywords Search(AC自动机初步)

题目大意: 给出多个模式串和一个主串,求多少个模式串在主串中出现过. 传送门 这是一道AC自动机的模板题. 在学习AC自动机之前,首先要学习WA自动机.TLE自动机和MLE自动机(雾 AC自动机是一种多模式串匹配算法. AC自动机概述: *fail指针:指向失配时的匹配节点: 1)构建字典树 2)初始化fail指针: 一条$fail$指针链可以理解为一个串连所有后缀相同的字符串的链表,并且所有链表的末端都指向trie的根.我们定义沿着节点$v$的$fail$指针走到根部的路径为v的trie链表.

【BZOJ】2434: [Noi2011]阿狸的打字机 AC自动机+树状数组+DFS序

[题意]阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母. 经阿狸研究发现,这个打字机是这样工作的: l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后). l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失. l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失. 我们把纸上打印出来的字符串从1开始顺序编号,一直到n.打字机有一个非