前言

一直听说\(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. remap——ROS中修改订阅的节点名称

    跑数据集或者使用不同传感器时,难免会遇到需要修改topic名称的时候,此时可以有两种做法. 一.直接修改源码.如果有launch文件,则修改launch文件对应的topic 二.直接进行remap操作 ...

  2. 使用js页面添加或删除标签

    // 添加var container = document.getElementById('divAudio');container.appendChild(audio); // 删除var cont ...

  3. Wannafly挑战赛27B(DFS,链表头插法)

    #include<bits/stdc++.h>using namespace std;int cnt=0;int flag=0;int to[400007],nex[400007],vis ...

  4. (PHP)redis Zset(有序集合 sorted set)操作

    /** * * Zset操作 * sorted set操作 * 有序集合 * sorted set 它在set的基础上增加了一个顺序属性,这一属性在修改添加元素的时候可以指定,每次指定后,zset会自 ...

  5. 洛谷P3138 [USACO16FEB]负载平衡Load Balancing_Silver

    P3138 [USACO16FEB]负载平衡Load Balancing_Silver 题目描述 Farmer John's NN cows are each standing at distinct ...

  6. Helvetic Coding Contest 2016 online mirror C2

    Description Further research on zombie thought processes yielded interesting results. As we know fro ...

  7. CentOS 7: ping Name or service not known

    解决方法 cd到/etc/sysconfig/network-scripts/ 修改ifcfg-*(*代表的内容可通过ifconfig查看)中的ONBOOT=yes reboot ifcfg-ens3 ...

  8. 修改apk里面的源码

    1.解压apk文件,获取classes.dex并拷贝到资源根目录(使用zip或其他解压工具即可) 2.使用baksmali工具将classes.dex转为smali文件,在命令行定位到资源根目录并执行 ...

  9. js 提示样式 ? 上写提示内容

    //再需要的地方放入 <img dms_map_key="zs_prise" src="${ctx }/static/image/tip.png" cla ...

  10. Java实例学习——企业进销存管理系统(3)

    Java实例学习--企业进销存管理系统(3) (本实例为书上实例,我所记录的是我的学习过程) 开始时间:2月12日 完成时间:暂未完成 2月16日-公共类(Item公共类,数据模型公共类,Dao公共类 ...