KMP、AC自动机在字符串匹配类动态规划问题中的应用

标签: kmp ac 自动机 | 发表时间:2011-10-30 11:22 | 作者:Mato_No1 Sosi
出处:http://www.cppblog.com/MatoNo1/
有一类动态规划(其中也包含递推)问题,要求满足一些限制条件的字符串,这些限制条件是“需要含有某个子串”或“不能含有某个子串”,那么KMP、AC自动机等就有大用了。

【例1】HDU3689
题意:字符集中有一些字符,给出每个字符的出现概率(它们的和保证为1),再给出一个子串B,求:任给一个长度为N的字符串A(只能包含字符集中的字符),使得S是A的子串的概率。

求解这类问题首先要进行补集转化。因为子串可能有重叠(比如"ababa"中就出现了两个"aba"),所以先转化为“求任给一个长度为N的字符串A(只能包含字符集中的字符),使得B不是A的子串的概率”,然后再用1减去这个概率即为结果。
设F[i][j]为“在所有长度为i的不出现B的字符串中,后缀与B的前缀匹配长度为j(即该字符串的后缀与B的前缀的最大匹配长度为j)的概率”,很显然,F是由递推得到了,关键是如何进行状态转移?或者说,在递推过程中,哪些状态可能成为F[i][j]的前趋状态?
假设F[i-1][k]是F[i][j]的前趋状态,也就是说,在字符集中至少存在一个字符c,使得主串的第i位(最后一位)取c时,能够从F[i-1][k]转移到F[i][j]。这就需要求一个值S[k][c],表示当主串的后缀与B的前缀的(最大)匹配长度为k时,在主串后再加上一个字符c,其匹配长度会变成什么。举例:设目前主串A'="abasab",B="asabs",其匹配长度为4,若在A'后加上一个字符's',则匹配长度变为5,所以S[4]['s']=5,而若在A'后加上一个字符'a',则匹配长度会变成1,所以S[4]['a']=1。显然S值和A前面的哪些字符是没有关系的。
那么这个S值如何计算?其实可以发现,S和KMP算法中的nx数组神似,因此完全可以按照计算nx数组的办法来计算S。具体来说,先要对B作KMP自身匹配,求出其nx数组,然后,在求S[k][c]的时候,尝试在B的第k位(由于B的下标从0开始所以B[k-1])后加上字符c,看看会“回退”到哪里即可。代码:
     int j = 0; nx[0= 0;
     re2(i, 
1, m) {
            
while (j && A[i] != A[j]) j = nx[j - 1];
            
if (A[i] == A[j]) j++;
            nx[i] 
= j;
     }
     re(i, m) re(k, SZ) {
           j 
= i;
           
while (j && A[j] != k + 97) j = nx[j - 1];
           
if (A[j] == k + 97) S[i][k] = ++j; else S[i][k] = 0;
     }
这里m是B的长度。注意,当i=m时,S[i][j]是无意义的,因为前面已经说过了不能出现B。
在求出S值后就能求出F值了。对于状态F[i][j],若存在一个字符c使得x=S[i][c](满足0<=x<m),则F[i][j]是F[i+1][x]的前趋状态。当然,由于本题是求概率而不是求总数,且每个字符出现的概率还不一样,所以转移的时候,应是将F[i+1][x]加上F[i][j]*P[c](P[c]是字符c出现的概率),边界:F[0][0]=1,F[0][1..m-1]均为0。
最终结果为1-∑F[N][0..m-1]。

代码

【例2】PKU1625URAL1158
题意:给出一些子串,求长度为N,各个字符都属于给定的字符集的所有字符串中,不包含任何一个给出的子串的字符串个数(需要使用压9位的高精度)。

本题显然是【例1】的多子串形式,而用来解决多个字符串同时匹配的只有AC自动机,那么如何在本题中使用AC自动机求解呢?
观察【例1】中的F[i][j],可以想象一下,一个图中有m个顶点,分别表示匹配长度为0..(m-1),然后不断新加入的字符让这些状态在这些结点间不断转移(状态转移就是图中的边),这样,F[i][j]就表示“阶段i到达结点j上”。而AC自动机是基于Trie(树)的,其中有现成的结点,这就揭示了本题的状
F[i][j]长度为i的合法的字符串(就是满足字符集限制且不包含任何一个给定子串)中,在匹配到最后一位(第i位)后,刚好到达结点j的字符串的个数
同样,S[k][c]表示“目前到达结点k,接下来的一个字符是c的时候,会到达哪个结点。在对所有的子串建立了自动机之后,S值只要类似地搞就能求出来了。然后F的转移也就搞定了。
不过,本题要万分注意AC自动机的一个BUG:在建立了自动机以后,需要把所有本身不危险(如果一个结点代表的字符串刚好是某一个给出的不能出现的子串,则该结点是危险结点),但通过失败指针不断上溯能够到达一个危险结点的结点,也标记为危险结点,比如两个子串是"abcde"和"bc",则代表"abcd"的那个结点由于包含了"bc"所以也是危险的。
此外,本题的输入要注意,字符集的ASCII码范围是-128~127,所以必须用char而不是unsigned char,且由于可能包含空格所以必须用gets()而不是scanf()输入,又因为C/C++中木有负数下标,因此在输入之后还要转化一下(加128)。

代码

【例3】PKU3691
题意:给出一些子串和一个字符串A(其每个字符均属于字符集{'A', 'C', 'G', 'T'}),求至少要改动A的几个字符(不能改成不属于字符集的字符),使得它不包含任何一个给出的子串,若不管怎么改都不行,则结果为-1。

这就是真正的DP了。设F[i][j]为前i位,到达的结点为j,最少改动的字符个数,则转移方程为
F[i][j] = min{F[i-1][x] + (A[i] != c)},c∈{'A', 'C', 'G', 'T'},S[x][c]=j。边界:F[0][root]=0,其余的F[0][]=+∞,A的实际下标从1开始。
求S数组的方法见【例2】

代码

【例4】PKU3208
题意:含有连续的三个数字6的正整数,称为"beastly number",求第P个(1<=P<=50000000)"beastly number"(其位数不会超过15位)。
(这题是本沙茶在PKU上至今为止,自己想出算法的AC人数最少的题)
本题其实是用不着KMP的,因为"666"这样简单的子串……

思路:由于位数不会超过15位(后来发现最多只有10位),所以每个"beastly number"都可以看成一个长度为15,字符集为['0'..'9']的字符串(注意是可以有前导0的,因为位数可能不足15位)A,整个过程也就是从高位(第0位)向低位(第14位)求出A的各位。

预处理:求出F[i][j],表示若A的前i位已经确定(其中不含"666",准确来说是非末尾不含"666"),且前i位的末尾刚好有j个'6'(j的范围是0到3)时,有多少个"beastly number"(注意,前i位既然已经确定,就不可更改了,能够决定的只有第i位的后面)。
显然先要求出F0[i][j]表示有多少个不是"beastly number"。其递推方程不好写,见代码(其实也是很好理解的)。然后F[i][j]=1014-i - F0[i][j]。

然后就是不断调整边界来构造了。准确来说,设前i-1位已经确定,现在要确定第i位,则枚举第i位是0~9中的哪个值,然后求出满足条件的最小的"beastly number"和最大的"beastly number"的名次(注意,名次是从1开始的),看看P在不在其中,这样就能确定了。严重注意:如果已确定的位数中已经出现了"666",接下来的就不用枚举了,直接在后面接上P-L就行了,L为左边界。

代码

总结:
KMP算法和AC自动机的状态转移性质决定了它们在字符串匹配类DP问题中的巨大作用。在实际应用中,要注意灵活使用它们。此外,AC自动机的那个BUG是一定要注意的。

Mato_No1 2011-10-30 11:22 发表评论

相关 [kmp ac 自动机] 推荐:

AC自动机

- Sosi - C++博客-Mato is No.1
AC自动机就是在Trie树上加入一些失败指针(fail,类似KMP中的next),使得它在某个结点失配的时候能够转移到该结点失败指针指向的结点继续匹配,从而实现多串匹配(单主串多子串). 其中SZ是字符集的大小,比如小写字母集SZ=26,数字集SZ=10等. 另外这个mul表示的是该结点的重复次数(和平衡树中的比较像),就是这个结点所对应的字符串(从根到该结点路径上的所有边上的字符组成的字符串)出现了几次.

KMP、AC自动机在字符串匹配类动态规划问题中的应用

- Sosi - C++博客-Mato is No.1
有一类动态规划(其中也包含递推)问题,要求满足一些限制条件的字符串,这些限制条件是“需要含有某个子串”或“不能含有某个子串”,那么KMP、AC自动机等就有大用了. 题意:字符集中有一些字符,给出每个字符的出现概率(它们的和保证为1),再给出一个子串B,求:任给一个长度为N的字符串A(只能包含字符集中的字符),使得S是A的子串的概率.

字符串匹配的KMP算法

- - 博客园_知识库
   字符串匹配是计算机的基本任务之一.   举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD".   许多算法可以完成这个任务, Knuth-Morris-Pratt算法(简称KMP)是最常用的之一. 它以三个发明者命名,起头的那个K就是著名科学家Donald Knuth.

字符串匹配 KMP 算法 Java实现

- - ITeye博客
字符串匹配过程中,如果使用蛮力算法,效率非常的差,在此介绍一种较为高效的匹配算法KMP算法. 其主要思想是从匹配的模版去分析,即去分析Pattern串的自身规律,进而去优化匹配的效率. 例如字符串“ababcb”,明显看出是ab出现一组重复,若出现如下匹配模式:. 此时发生错误,一般情况下会选择移动Pattern一个位置来继续,事实证明效果不佳.

字符串匹配(BF,BM,Sunday,KMP算法解析)

- - CSDN博客综合推荐文章
字符串匹配一直是计算机领域热门的研究问题之一,多种算法层出不穷. 字符串匹配算法有着很强的实用价值,应用于信息搜索,拼写检查,生物信息学等多个领域. 今天介绍几种比较有名的算法:. BF(Brute Force)算法又称为暴力匹配算法,是普通模式匹配算法. 其算法思想很简单,从主串S的第pos个字符开始,和模式串T的第一个字符进行比较,若相等,则主串和模式串都后移一个字符继续比较;若不相同,则回溯到主串S的第pos+1个字符重新开始比较.

AC-Comparatives发布2011年度总结报告,卡巴斯基夺冠

- - 奶味网-IT人- 最新RSS订阅
著名评测机构AV-Comparatives发布了它的2011年度总结报告. 在报告中,时隔5年,卡巴斯基终于再次夺冠,获得2011年度产品称号. 从左到右分别为手动潜在有害程序检测测试(二月)、启发式智能检测测试(二月)、性能测试(套装)、全功能动态保护测试(part 1)、手动潜在有害程序检测测试(八月)、启发式智能检测测试(八月)、性能测试(AV部分)、病毒清除测试、全功能动态保护测试(part 2).

转载一篇单字符串匹配KMP算法最好理解的文章

- - 编程语言 - ITeye博客
字符串匹配是计算机的基本任务之一.   举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD".   许多算法可以完成这个任务,. Knuth-Morris-Pratt算法(简称KMP)是最常用的之一. 它以三个发明者命名,起头的那个K就是著名科学家Donald Knuth.

Levenshtein 自动机(拼音纠错)

- -
原文: http://blog.jobbole.com/80659/. 在上一期的超酷算法中,我们聊到了BK树,这是一种非常聪明的索引结构,能够在搜索过程中进行模糊匹配,它基于编辑距离 (Levenshtein distance),或者任何其它服从三角不等式的度量标准. 今天,我将继续介绍另一种方法,它能够在常规索引中进行模糊匹配搜索,我们将它称之为 Levenshtein自动机.

遥控玩具 Wall-E 改装为声控、有眼睛的半自动机器人,更加可爱俏皮

- kebo - Engadget 中国版
千万不要被这个凸出来的眼睛吓倒喔. 其实你现在盯着的是来至加拿大的 DJ Sures 的杰作:他为遥控玩具 Wall-E (瓦力)安装了一个镜头,然后再用 EZ-B Robot Controller 的软件来加入颜色、动作、语音命令和人脸办识功能. 仔细看看,你也会留意到这台用 AA 电池的机器人也配置了几个电动马达,不仅让头部跟随人脸或者小球来移动,也可以叫它挥手跳舞呢(跳转后有影片).