前言

一直听说\(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. Spring MVC 基于Method的映射规则(注解版)

    在Restful风格的web开发中,根据不同的请求方法使用相应的控制器处理逻辑成为核心需求,下面就看看如何在Spring MVC中识别不同的请求方法. 请求方法 在Http中,请求的方法有很多种,最常 ...

  2. 避免picture图片无法删除,提示正在被其他进程使用

    pictureBox1.Image = Image.FromStream(ByteToStream(SetImageToByteArray(cutImgPath))); #region 将文件转换成流 ...

  3. 数据返回正常 而header头Status=500

    访问接口数据成功返回,但header头Status Code:500,而调用接口的html用ajax访问一直返回500服务器错误,并且ajax一直走error,走不到success中,错误返回状态是连 ...

  4. Java通过IO流输入输出 向文件中存入大量三个属性的值,并通过验证前两个属性输出第三个属性

    package ABC1; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import ...

  5. springboot 简单自定义starter - beetl

    使用idea新建springboot项目beetl-spring-boot-starter 首先添加pom依赖 packaging要设置为jar不能设置为pom<packaging>jar ...

  6. 【JavaScript权威指南】——逻辑与(&&)

    三种用法总结: 1.布尔值计算: [成员]={false,true} 2.“真值”,“假值”计算: [假值]={false,null,undefined,0,-0,NaN,""} ...

  7. Sharepoint 根据文件相对路径获取、操作SPFolder

    public AjaxResult LoadDocInfo(HttpContext httpContext) { var result = new ArrayList(); try { var org ...

  8. CF D. Recovering BST (区间DP)

    题意:给你n个节点,每个节点有一个权值,两个点可以连边当且仅当这两个点的gcd>1,问你这n个点能否构成一个二叉搜索树(每个节点最多有两个儿子,且左儿子小于右儿子),输入为递增顺序. 分析: 若 ...

  9. Eclipse中mybatis的xml文件没有提示,出现the file cannot be validated as the XML definition.....

    1.下载dtd文件 2.在eclipse中配置本地dtd文件: Window->Preferences->XML->XML Catalog->User Specified En ...

  10. Unity C# 反射

    什么是反射 在.NET中的反射也可以实现从对象的外部来了解对象(或程序集)内部结构的功能,哪怕你不知道这个对象(或程序集)是个什么东西,另外.NET中的反射还可以运态创建出对象并执行它其中的方法. 反 ...