前言

一直听说\(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. MATLAB求解非齐次线性方程组

    例如方程组: 法1:左除法 >> A=[3 1 -1;1 2 4;-1 4 5];b=[3.6;2.1;-1.4]; >> x=A\b x = 1.4818 -0.4606 0 ...

  2. matlab矩阵与数组

    数组运算:数与数组加减:k+/-A %k加或减A的每个元素数组乘数组: A.*B %对应元素相乘数组乘方: A.^k %A的每个元素k次方:k.^A,分别以k为底A的各元素为指数求幂值数除以数组: k ...

  3. 死磕 java同步系列之synchronized解析

    问题 (1)synchronized的特性? (2)synchronized的实现原理? (3)synchronized是否可重入? (4)synchronized是否是公平锁? (5)synchro ...

  4. CPU使用情况检测

    改编自:https://blog.csdn.net/Yan_Chou/article/details/80456995 检测命令整理: dd iotop df top psiostatvmstatne ...

  5. yum 缓存包到本地

    yum install –downloadonly –downloaddir=/root/mypackages/ vim 说明: --downloadonly 只下载 --downloaddir 下载 ...

  6. 通过用axios发送请求,全局拦截请求,获取到错误弄明白promise对象

    axios.interceptors.response.use(function (response) { // 对响应数据做点什么 return response; }, function (err ...

  7. Python网络爬虫(三)

    AJAX学习 AJAX=Asynchronous JavaScript and XML(异步的 JavaScript 和 XML).通俗来说,AJAX是一种无需加载整个网页的情况下,通过在后台与服务器 ...

  8. codeforces round 472(DIV2)D Riverside Curio题解(思维题)

    题目传送门:http://codeforces.com/contest/957/problem/D 题意大致是这样的:有一个水池,每天都有一个水位(一个整数).每天都会在这一天的水位上划线(如果这个水 ...

  9. POJ2945 Find the Clones trie树

    建一颗$trie$树(当然你哈希也资瓷),边插边更新,看看搜到最底时有多少个字符串,然后更新. #include<cstdio> #include<iostream> #inc ...

  10. BZOJ 3673 可持久化并查集 by zky && BZOJ 3674 可持久化并查集加强版 可持久化线段树

    既然有了可持久化数组,就有可持久化并查集.. 由于上课讲过说是只能按秩合并(但是我也不确定...),所以就先写了按秩合并,相当于是维护fa[]和rk[] getf就是在这棵树中找,直到找到一个点的fa ...