我是连月更都做不到的蒟蒻博主QwQ

考虑到我太菜了,考完noip就要退役了,所以我决定还是把博客的倒数第二篇博客给写了,也算是填了一个坑吧。(最后一篇?当然是悲怆のnoip退役记啦QAQ)

所以我们今天学习的是AC自动机的Trie图和last优化。如果不知道什么是AC自动机,建议看一看我的上一篇博客:AC自动机学习笔记1

Trie图

上次我们说到朴素的AC自动机的时间复杂度是布星的,原因如下:

匹配时因为每次都要跳fail边,复杂度上界可以达到 $ O(ml) $

而Tire图就是用来解决这种问题的。可以想到,匹配时跳fail边是十分浪费时间的。举个例子,对于字符集{a,b,c}上的模式ab,aab,aaab,aaaab,ac和文本串aaaac,它们建出来的AC自动机和匹配过程是这样的(蓝色边是Trie树的边,红色边是fail指针,黄色边是匹配时的状态转移):

我们会想,如果失配时可以一步到位就好了。每次跳fail边的过程是固定的:一直跳,直到找到拥有儿子c的节点为止。也就是说,无论什么时候在这个节点上失配,只要你找的是字符c,你总会在固定的节点上重新开始匹配。既然这样,不如直接把那个字符为c的节点变成自己的儿子,就可以省去跳fail边的麻烦:

上图中,所有的节点的a,b,c三个子节点都是满的(未画出的边都指向根节点,表示完全失配只能从根重新开始)。这样,原本是DAG结构的AC自动机上出现了环,这样的结构我们称之为Trie图。于是乎,在匹配的时候我们终于可以不用考虑fail边,一口气不停地匹配到底辣٩(๑>◡<๑)۶复杂度变成了真正的 $ O(m) $ ,所以你就可以拿这个算法去爆踩std啦qwq

那么,怎么利用fail指针将AC自动机转化为Trie图呢?其实,只需要在构建fail指针时顺便修改子节点就行了:

void build()
{
queue<int>q;
q.push(1);
while(!q.empty())
{
int x=q.front();q.pop();
for(int i=0;i<26;++i)
{
int c=ch[x][i];
if(!c){ch[x][i]=ch[fail[x]][i];continue;}//关键,把子节点改成fail节点的子节点
q.push(c);
int fa=fail[x];
while(fa&&!ch[fa][i])fa=fail[fa];
fail[c]=ch[fa][i];
}
}
}

因为当你遍历到这个节点时,fail节点的所有儿子肯定已经求出来了,所以直接用fail节点的子节点就好了。

last优化

上述方法将建图+匹配的复杂度成功优化为了 $ O(\sum 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优化是对复杂度没有影响的小优化,但是大多数情况下效果明显,类似于搜索剪枝。

总结

trie图和last优化都是在“如何跳过不必要的操作”上进行思考后的产物。这种思想可以被运用在很多题目里面,往往可以把复杂度里的一个n给去掉或者变成log。(不存在的。。。所谓“把某种方法完全掌握就可以轻松做出所有这种题”是某C姓教练最喜欢说的话,他认为“没做出一道要用到某种数据结构的题”的原因是“对某种数据结构的掌握还是不够熟练”,进而认为最好且明智的解决方法就是“多刷这种数据结构的题以提高熟练度”。这种人实在不好评价,我们还非得听他的话。。。)

AC自动机学习笔记就告一段落了,写这样一篇博客真的很费劲,感谢您的资瓷啦qwq!

AC自动机学习笔记-2(Trie图&&last优化)的更多相关文章

  1. [AC自动机][学习笔记]

    用途 AC自动机适用于一类用多个子串在模板串中匹配的字符串问题. 也就是说先给出一个模板串,然后给出一些子串.要求有多少个子串在这个模板串中出现过. KMP与trie树 其实AC自动机就是KMP与tr ...

  2. AC自动机板子题/AC自动机学习笔记!

    想知道484每个萌新oier在最初知道AC自动机的时候都会理解为自动AC稽什么的,,,反正我记得我当初刚知道这个东西的时候,我以为是什么神仙东西,,,(好趴虽然确实是个对菜菜灵巧比较难理解的神仙知识点 ...

  3. AC自动机学习笔记-1(怎么造一台AC自动机?)

    月更博主又来送温暖啦QwQ 今天我们学习的算法是AC自动机.AC自动机是解决字符串多模匹配问题的利器,而且代码也十分好打=w= 在这一篇博客里,我将讲解AC自动机是什么,以及怎么构建一个最朴素的AC自 ...

  4. AC 自动机学习笔记

    虽然 NOIp 原地爆炸了,目前进入 AFO 状态,但感觉省选还是要冲一把,所以现在又来开始颓字符串辣 首先先复习一个很早很早就学过但忘记的算法--自动 AC AC自动机. AC 自动机能够在 \(\ ...

  5. 【AC自动机】【字符串】【字典树】AC自动机 学习笔记

    blog:www.wjyyy.top     AC自动机是一种毒瘤的方便的多模式串匹配算法.基于字典树,用到了类似KMP的思维.     AC自动机与KMP不同的是,AC自动机可以同时匹配多个模式串, ...

  6. AC自动机学习笔记

    AC自动机 ----多个模板的字符串匹配 字典树Trie加上失配边构成 插入操作:ac.insert(p[i],i);构造失配函数:ac.getFail();计算文本串T中每个模板串的匹配数:ac.f ...

  7. AC自动机学习

    今天包括这一周开始学习AC自动机了,有点晚,但我感觉努努力还来得及.4月份还得认认真真攻图论,加油! 为2个月后的邀请赛及省赛.东北赛做准备. 推荐AC自动机学习地址:http://www.cppbl ...

  8. GIS案例学习笔记-ArcGIS整图大图出图实例教程

    GIS案例学习笔记-ArcGIS整图大图出图实例教程 联系方式:谢老师,135-4855-4328,xiexiaokui#qq.com 1. 通过出图比例尺(1:2000),地图范围测算图纸大小. 图 ...

  9. UML学习笔记:类图

    UML学习笔记:类图 有些问题,不去解决,就永远都是问题! 类图 类图(Class Diagrame)是描述类.接口以及它们之间关系的图,用来显示系统中各个类的静态结构. 类图包含2种元素:类.接口, ...

随机推荐

  1. BZOJ2169 连边(动态规划)

    令f[i][j]表示连i条边时奇点个数为j的方案数,转移时讨论两奇点相连.一奇一偶相连.两偶点相连即可.注意这样会造成重边,那么算出恰好有一条重边的方案数并减掉.由于是有序地考虑每条边,每次还要除以i ...

  2. THUWC2019爆零记

    Day -1 现在在机房里,准备敲敲板子什么的. 今天晚上放假诶,要好好睡一下.好好睡是不可能的,这辈子不可能的. Day 0 现在在酒店,\(lwh\)神仙在超越,我打了个\(treap\)的板子就 ...

  3. 【BZOJ4883】棋盘上的守卫(最小生成树)

    [BZOJ4883]棋盘上的守卫(最小生成树) 题面 BZOJ 题解 首先\(n\)行\(m\)列的棋盘显然把行列拆开考虑,即构成了一个\(n+m\)个点的图.我们把格子看成边,那么点\((x,y)\ ...

  4. 【uoj428】普通的计数题

    Portal --> uoj428 Solution 不会胖子的一个log正解qwq只能怂怂滴写分治了qwq ​ 首先就是一个我想不到的转化qwq ​ 我们将第\(i\)次操作加入的数看成一个编 ...

  5. 【codevs2205】等差数列

    题目大意:给定一个长度为 N 的序列,求这个序列中等差数列的个数. 题解:根据题意应该是一道序列计数 dp.设 \(dp[i][j]\) 表示以第 i 项结尾,公差为 j 的等差数列的个数,则状态转移 ...

  6. .net MVC入门

    这里面之所以没有Sql语句但是也可以对数据库进行数据操作的原因就是Entity Framework.Entity Framework有三种模式,这里用的是Models模式. 网上有太多的.net MV ...

  7. linux系统root密码忘了怎么办 三种方法快速找回root密码

    linux root密码找回方法一 第1步:在系统进入单用户状态,直接用passwd root去更改. 第2步:用安装光盘引导系统,进行linux rescue状态,将原来/分区挂接上来,作法如下: ...

  8. pgadmin导出excel

    生成导入sql 第一行公式:="insert into province(code,name) values("&A2&",'"&B2& ...

  9. 织梦 dedecms 首页调用公司简介的内容

    首页调用公司简介的代码: {dede:sql sql='Select content,substring(content,1,300) as content from dede_arctype whe ...

  10. [整理]Git使用文章整理

    http://pcottle.github.io/learnGitBranching/ http://www.ruanyifeng.com/blog/2012/07/git.html