前言

一直听说\(AC\)自动机是一个很难很难的算法,而且它不在\(NOIP\)提高组范围内(这才是关键),所以我一直没去学。

最近被一些字符串题坑得太惨,于是下定决心去学\(AC\)自动机。

简介

\(AC\)自动机是一个著名的多模字符串匹配算法,建立在\(KMP\)算法\(Trie\)字典树的基础之上。

其实,它的本质就相当于在一棵\(Trie\)上跑\(KMP\),真是一个十分强势的算法。

\(Trie\)的作用

不得不说,在\(AC\)自动机的实现中,\(Trie\)起到了很大的作用:因为我们用它存下了每一个用来与文本串匹配的模式串。

我们可以新建一棵\(Trie\)如下:

struct Trie
{
    int Son[26],sum,Next;//Son记录当前节点的儿子的位置,sum记录当前节点包含的字符串个数,Next记录失配指针
}node[N+5];

然后将每一个模式串插入\(Trie\)中,就完成一开始的存储部分了:

inline void Insert(string s)//Trie的插入真的十分简洁
{
    register int i;int x=rt;//x记录当前节点
    for(i=0;i<s.length();++i)
    {
        int p=s[i]-'a';//p记录下一个节点的编号
        if(!node[x].Son[p]) node[x].Son[p]=++tot;//如果下一个节点不存在,就新建一个节点
        x=node[x].Son[p];//将x更新为下一个节点
    }
    ++node[x].sum;//将最终到达的节点所包含字符串的个数加1
}

神奇的失配指针

\(AC\)自动机(\(KMP\)算法)的精髓就在于失配指针\(Next\)(许多人把\(AC\)自动机的失配指针称为\(fail\),不过,由于我习惯把预处理出\(KMP\)中的\(Next\)数组的函数称为\(GetNext()\),换成\(GetFail()\)恐怕不太吉利......因此我依然用\(Next\)来表示失配指针)。

与\(KMP\)中的失配指针有点区别,\(AC\)自动机中的失配指针指向的是当前匹配到的字符串的最长后缀

如何求失配指针

我们可以写一个函数\(GetNext()\)来求出失配指针。

记得我在有关\(KMP\)的一篇博客中提到过,求\(Next\)数组的过程就是一个\(KMP\)的过程,不得不说,\(AC\)自动机也是类似的。

不过,求失配指针的过程有点像一个\(BFS\),我们可以用一个队列来存储访问到的字符串,然后每次都求出队首的一个字符串(这样可以保证每次取出的字符串的长度是递增的),求出它的失配指针。

代码如下:

inline void GetNext()//求出失配指针,类似于广搜
{
    register int i,k;q.push(rt);//初始化队列
    while(!q.empty())//只要队列中还有元素
    {
        k=q.front(),q.pop();//取出队首的元素
        for(i=0;i<26;++i)//枚举这个元素的每一个子节点
        {
            if(k^rt)//如果当前的元素不是根节点
            {
                if(!node[k].Son[i]) node[k].Son[i]=node[node[k].Next].Son[i];//如果当前节点这个儿子不存在,就将当前节点的失配指针的儿子作为当前节点的儿子
                else node[node[k].Son[i]].Next=node[node[k].Next].Son[i],q.push(node[k].Son[i]);//如果当前节点有这个儿子,就将当前节点的儿子的失配指针指向当前节点的失配指针的这个儿子,并将当前节点加入队列
            }
            else//如果当前元素是根节点就特殊处理
            {
                if(!node[k].Son[i]) node[k].Son[i]=rt;
                else node[node[k].Son[i]].Next=rt,q.push(node[k].Son[i]);
            }
        }
    }
}

\(AC\)自动机的简单实现

好了,讲完了失配指针,\(AC\)自动机的核心代码应该就很简单了吧。

这里以洛谷上一道简单的板子题为例,来贴一份代码:

inline void AC_Automation()//AC自动机的核心代码
{
    register int i,j,x=rt,len=st.length();//x记录当前到达节点
    for(GetNext(),i=0;i<len;++i)//枚举文本串上的每一个字符
    {
        if(!(x=node[x].Son[st[i]-97])) {x=rt;continue;}
        int p=x;//用p来记录当前能匹配到的字符
        while(p^rt)//只要p没有指向根
        {
            if(node[p].Cnt>=0) ans+=node[p].Cnt,node[p].Cnt=-1;//如果当前节点未被访问过,就更新匹配成功的字符串个数,并标记当前节点为已访问
            else break;//否则退出循环,因为如果当前节点访问过了,那么当前节点失配指针指向的位置肯定也访问过了
            p=node[p].Next;//更新当前节点为当前节点的失配指针
        }
    }
}

\(AC\)自动机的小应用

毕竟,\(AC\)自动机的题目不可能直接出裸题让你做字符串匹配的。

通常都只是一些小应用:

【洛谷3796】【模板】AC自动机(加强版)

【BZOJ4327】[JSOI2012] 玄武密码

【BZOJ3940】[USACO2015 Feb] Censoring

【BZOJ3172】[TJOI2013] 单词

初学AC自动机的更多相关文章

  1. bzoj 3172 AC自动机

    初学AC自动机,要先对于每一个模式串求出来trie树,在此基础上构建fail指针,然后在trie树加上失配边构建出整张trie图. AC自动机的原理和KMP差不多,一个节点的fail指针就是指向tri ...

  2. Hdu 5384 Danganronpa (AC自动机模板)

    题目链接: Hdu 5384 Danganronpa 题目描述: 给出n个目标串Ai,m个模式串Bj,问每个目标串中m个模式串出现的次数总和为多少? 解题思路: 与Hdu 2222  Keywords ...

  3. Codeforces 547E - Mike and Friends(AC 自动机+树状数组)

    题面传送门 好久每做过 AC 自动机的题了--做几个题回忆一下罢 AC 自动机能够解决多串匹配问题,注意是匹配,碰到前后缀的问题那多半不在 AC 自动机能解决的范围内. 在初学 AC 自动机的时候相信 ...

  4. 基于trie树做一个ac自动机

    基于trie树做一个ac自动机 #!/usr/bin/python # -*- coding: utf-8 -*- class Node: def __init__(self): self.value ...

  5. AC自动机-算法详解

    What's Aho-Corasick automaton? 一种多模式串匹配算法,该算法在1975年产生于贝尔实验室,是著名的多模式匹配算法之一. 简单的说,KMP用来在一篇文章中匹配一个模式串:但 ...

  6. python爬虫学习(11) —— 也写个AC自动机

    0. 写在前面 本文记录了一个AC自动机的诞生! 之前看过有人用C++写过AC自动机,也有用C#写的,还有一个用nodejs写的.. C# 逆袭--自制日刷千题的AC自动机攻克HDU OJ HDU 自 ...

  7. BZOJ 2434: [Noi2011]阿狸的打字机 [AC自动机 Fail树 树状数组 DFS序]

    2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2545  Solved: 1419[Submit][Sta ...

  8. BZOJ 3172: [Tjoi2013]单词 [AC自动机 Fail树]

    3172: [Tjoi2013]单词 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 3198  Solved: 1532[Submit][Status ...

  9. BZOJ 1212: [HNOI2004]L语言 [AC自动机 DP]

    1212: [HNOI2004]L语言 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1367  Solved: 598[Submit][Status ...

随机推荐

  1. Spark Streaming 的容错

    Spark Streaming 为了实现容错特性,接收到的数据需要在集群的多个Worker 节点上的 executors 之间保存副本(默认2份).当故障发生时,有两种数据需要恢复: 1. 已接收并且 ...

  2. SpringBoot2.0 基础案例(04):定时任务和异步任务的使用方式

    一.定时任务 1.基本概念 按照指定时间执行的程序. 2.使用场景 数据分析 数据清理 系统服务监控 二.同步和异步 1.基本概念 同步调用 程序按照代码顺序依次执行,每一行程序都必须等待上一行程序执 ...

  3. Luogu P5103 「JOI 2016 Final」断层 树状数组or线段树+脑子

    太神仙了这题... 原来的地面上升,可以倒着操作(时光倒流),转化为地面沉降,最后的答案就是每个点的深度. 下面的1,2操作均定义为向下沉降(与原题意的变换相反): 首先这个题目只会操作前缀和后缀,并 ...

  4. 牛客练习赛34-C-little w and Segment Coverage(差分数组)

    链接:https://ac.nowcoder.com/acm/contest/297/C 来源:牛客网 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 262144K,其他语言5242 ...

  5. FTP服务器windows配置

    1.配置IIS Web的FTP站点 1.1 打开iis管理器(方法很多,这里我们用命令) Win+r中输入:inetmgr 1.2 新建FTP站点 1.2.1 新建FTP服务器根目录文件夹 1.2.2 ...

  6. springcloud中常用的注解@

    @SpringBootApplication是springboot启动类,包括三个注解,他们的作用分别是: @Configuration:表示将该类作用springboot配置文件类 @EnableA ...

  7. 牛客网训练赛26D(xor)

    题目链接:https://www.nowcoder.com/acm/contest/180/D 线性基的学习:https://www.cnblogs.com/vb4896/p/6149022.html ...

  8. 牛客网Java刷题知识点之构造函数可以调用一般函数,但是一般函数不可以直接调用构造函数

    不多说,直接上干货! 通过 牛客网Java刷题知识点之构造函数是什么.一般函数和构造函数什么区别呢.构造函数的重载.构造函数的内存图解 我们对构造函数有了一个比较清楚的认识,当我们在创建对象时,我们会 ...

  9. [Java][Liferay] 解决Liferay ext项目deploy的问题

    Liferay ext project在install war包之后需要重启服务器,重启服务器中会执行ExtHotDeployListener中的逻辑,这里有一个坑,如果是第二次以后install e ...

  10. .net core +mysqlSugar(最为简单的增删改查)

    首先建立.net Core API - empty 这个就不说了 然后创建新的Controller 记得添加路由 [Route("api/Users")] 然后在Nuget Pac ...