文不达意,口齿不清,思想混乱,令人喷饭。(估计只有我自己才能看懂我在说什么)简书没有mathjax公式没法愉快显示
AC自动机(Aho-Corasick Automaton)是一个依次输入一个字符串S的各个字符,仅当AC自动机中保存的字符串出现在当前串S当中时,进入合法状态的自动机。AC自动机被构建在一棵Trie树上。我们可以像使用KMP算法时一样,对节点构造Fail指针,实现线性时间复杂度的字符串匹配功能。
基础内容
AC自动机被构建在Trie树上,节点有Fail指针。考虑Fail指针的性质,Fail指针只有可能是如下几种情况中的一种:
- Fail指向Trie树的根节点
- Fail指向父节点开始的由Fail指针构成的链上某个节点的子节点
如何理解这条性质呢?当Trie树上同时有串ABCE和BCD,对于目标串ABCD进行字符串匹配的时候,AC自动机读入目标串中D的时候,发现和ABCE中的E不同,于是跳转到BCD中的D进行匹配。所以除了父节点的Fail指针指向根节点的情况外,节点的Fail指针满足上面的性质。于是有了构造Fail指针的办法:
/**
* 约定:
* CodeSet: 字符集,比如字母就是'a'~'z'
* trie[u][i]: Trie树的边,u为节点编号,i为下一个字符,我们将一些额外的边(匹配之用)也会存进去
* fail[u]: 失败指针,指向一个节点编号
* root: 根节点编号,这里设置为0
*/
void makeFail(int u) {
for (int i: codeSet) {
if (trie[u][i]!=null) {
int tmp=fail[u];
while (tmp!=root&&trie[tmp][i]==null)
tmp=fail[tmp];
fail[trie[u][i]]=(u==root)?0:trie[tmp][i];
}else {
trie[u][i]=trie[fail[u]][i]; // 将不存在的子节点指向fail指针引导的状态
}
}
}
构造Fail指针需要遵从一定的顺序,显然DFS是不可以的。因为搜寻Fail指针的时候,有的节点(例如相邻子树上的节点)Fail指针没有计算出来,所以无法推算。在这种时候应当用BFS进行搜寻。
在上面构建Fail指针的时候我们还进行了另外一种操作:就是将”不存在的子节点“指向了Fail指针引导的子节点。在这样的一张有向图上我们就可以轻松地进行字符串匹配了。
字符串匹配
就像KMP算法一样,AC自动机对字符串的匹配也是逐个读入字符,比对并进行状态转移(在上文提到的生成的有向图中)。当遇到合法节点的时候,我们进行计数。下面看这样的例子:在ABCDEF中查找ABC和BC的过程
- 依次读入ABC,AC自动机从根结点开始由A->B->C进行转移,我们发现ABC被成功匹配了。当读取D的时候,状态被转移到了根节点,因为没有字符能和D匹配
- 直到读完原字符串为止
问题:BC没有被匹配到。实际上在进入合法节点的时候,合法节点Fail指针形成的链上,所有的合法节点均被匹配上了。实际统计匹配成功数时,我们可以只在该节点上计数。在统计完毕之后,将Fail指针形成的树做一次树形dp:节点的匹配数等于子树根节点匹配数之和来计算总匹配数。
题目
AC自动机的题目形式相对固定,虽然不会出特别裸的字符串匹配题目。
AC自动机在ACM赛场上出现一般会作为引出题目的状态转移、或者模型的工具。AC自动机的作用可以归结如下:
- 字符串匹配(HDU3065)
- 字符串计数
- 字符串构造(引出模型)
HDU5955 一个骰子不停的扔,扔出谁的目标序列谁就赢了,问:每个人获胜的概率。其中每个人对应一种目标序列(由1~6中的整数构成,且一局游戏中每个人的序列长度相等而不同)。
对所有人的获胜序列构造AC自动机。AC自动机中可以得到扔骰子的状态转移过程:实际上AC自动机中某个节点包含了之前扔的几次的信息(比如说从Trie树的根节点到该节点路径上的字符构成的串,这些信息往往很有用;又比如说节点是否是游戏的终止状态——最后几次的结果和某个人的串对应,游戏结束)。对于自动机中某个节点u,它能转移到的节点(后继)v_i一共有6个(很显然扔一个立方体骰子能产生6个不同的数字之一),到达该节点的概率就等于sum_{1}^{6}P(v_i)/6。根据这个构造出增量矩阵A:AC自动机中有边u->v(u不是游戏结束状态),那么u行v列被设置为1/6。假设答案是b(n维列矩阵,记录了自动机每个节点状态的概率), 初始序列是x(除了0节点概率为1之外其它都是0)。有
b=sum_{i=1}{inf}(Ai)x
当上式收敛时,将它改写为b=(E-A)^{-1}x,即(E-A)b=x,接下来的任务就是用高斯消元求解b。b中所有结束状态的概率即为所求答案。
很可惜在下并没有弄清楚题目背后的数学原理
POJ2778 有m种DNA序列是有疾病的,问有多少种长度为n的DNA序列不包含任何一种有疾病的DNA序列。(仅含A,T,C,G四个字符)
- m=4,n=3,{“AA”,”AT”,”AC”,”AG”},答案为36,表示有36种长度为3的序列可以不包含疾病
- 疾病字符串最多10个,且长度<=10
很惭愧……等我研究懂了再说