再讲题目之前,先讲讲ac自动机吧
看到ac这么吉利的东西,学会这什么ac自动机的欲望便来了
ac自动机全名Aho-Corasick automation,挺长的,但经验告诉我们,名字越长往往编出来的代码越短,所以不要被这么虎的名字给唬住了
好了,进入正题
ac自动机就是用来进行多串模式匹配的一种算法(不算是一种什么神奇的数据结构吧),它差不多就等于trie+kmp
这个算法的思想非常简单,就在字母树上弄个next指针(相当于kmp中的next函数),然后对于每一篇文章我们就可以根据这棵字母树来O(len)的得到那些单词出现过(貌似要统计出现个数有难度,这恐怕就是这个算法的局限性吧)
这个算法的关键就是计算next[i],方法如下:
对于每个结点i,若它就是root,则next[i]=0,否则
在i的父亲的next找父链(就是一路j:=next[j]上去,显然不能算进i的父亲)上找到第一个点的儿子中有i带的字符的点,
next[i]=这个儿子,如果找不到,则next[i]=root
就这么简单,具体实现起来的话广搜就可以了
然后,给了一段文章,我们要拿他与给定的单词进行匹配,就用刚才做好的字母树+next指针直接做就可以了(像kmp那样)
特别要注意,每匹配完一位,就要在next链上往回找有没有单词恰好在这一位完成匹配,而已经出现了的单词,我们可以直接把它的标记去掉就可以了(下面的代码中带!!的地方需注意)
实现起来有个小的改动可以让代码更简洁,那就是把字母树中的c[0,i]赋为1,那么next链到底时,再直接用c[0,i]就可以回到root了(我的root=1)就不用每次(k<>root)and ...了,可以显得漂亮一点
下面给出我的ac_auto的模板
第一次写这个调了几个小时才过(就是!!那里一直出问题),后来才发现每个用ac自动机的题目都要注意这里!
讲完了算法,就来看看题目吧
pku3691
大意:给若干个单词,一段文章,要求这段文章至少改动几个才能不含任何一个单词,字母只有ACGT
分析:
这个题dp的方向很明显,只是状态的设计有点难度(在学会ac自动机之前,咳咳,我应该是做不出的)
那么我就先入为主了,考虑怎么在自动机的那棵字母树上做文章
可以想到如果匹配完了原串的前i位,当前到了字母树的j号结点,只要有了这个i和j就能够无后效地表示出整个匹配的状态了,那么算法就出来了
算法:
记:
f[i,j]表示匹配完了原串的前i位,当前到了字母树的j号结点的最少要改动的个数,转移就枚举第i+1位改成什么或者不改,用上述算法推到下一个状态就可以了,注意不能转移到的结点,要求不能成功匹配任意一个单词
边界f[0,1]=0,答案min{f[len,i]}(word[i]=false)
需要注意的是每要转移到一个结点,就得在next链上扫一遍,看有没有word[i]为true,有就不能转移了(上面提到过的),还有麻烦的输出格式和ansistring(我wa了3次格式,1次ansistring),多组数据还得记得清空数组
代码:
pku2778
大意:给你若干个单词,求长度为n的dna串(只含'A''C''G''T')中不含任何一个单词的串的个数(mod 100000)(n<=200000000)
分析及算法:
同2778类似的,dp的解题方向不会错
这个题目中,n这个天文数字般的范围似乎在告诉我们这题用是矩阵加速的dp题,那么我就只讲转移矩阵的构造了,关于矩阵优化你可以看网上其他的资料
对于字母树中的结点i,枚举下一个字符是什么,到了结点j,只要没有一个单词匹配成功(还是那个要注意的地方,要在next链上扫一遍)就可以转移,inc(g[i,j])即可
再快速幂,ans=sigma(f[1,i]) mod mo (word[i]=false) (我的root=1)
代码:
这道题写的相当流畅,一次ac,感觉反正是越来越好
好了,至此,ac自动机应该差不多就这样了吧,发现自动机主要是用来优化有关多串模式匹配的dp中的状态表示,以后碰到题目得往这方面想想了。希望ac自动机能给我们带来更多ac的喜悦吧!