2.多字符串匹配问题和Trie(字典树):
对于多字符串匹配问题,我们一般会用hash(散列表)或者Trie(字典树)储存。
a.hash:
将字符串利用hash函数映射到对应的hash值,然后将字符串插入对应函数值点的储存空间。(这里不关于hash函数的选择和具体实现方式。)
b.Trie:
这是一个树,输的节点有|{字符集}|个指针,如果一个单词对应的字母的x后面有字母y,那么他的y指针就指向一个新的节点。
上图是一个集合{he,hers,his,she}构成的字典树。
c.字典树的定义:
字典树的节点如下面,数据分为两部分;一部分是指针数组,用来指向单词的下一个字母;另一部分是数据域,存储单词结尾的标记、单词计数、或者是字符串之间映射的对应串。
d.新单词的插入:
从根节点开始查找,如果单词当前字母指针不空,则沿着这个指针查找;如果为空,则插入新的节点,沿着该节点方向查找。
新节点的插入:每次插入的新节点,初始化所有指针和数据域初始为空。
e.单词查询:
返回对应单词结束位置节点的save即可。
3.AC自动机的介绍:
在此认为大家已经有了 KMP算法以及Trie(字典树)的基础(如果,上面的讲述不够详细,还请查看相关资料)。AC自动机可以理解为 KMP算法的多模式串形式扩展。
那么什么是 AC自动机呢,通俗的说就是Trie的每个节点加上了一个fail指针,fail指针指向当前匹配失败的跳转位置,这就类似于KMP的next数组。
4.AC自动机的构造:
既然我们知道了 AC自动机是用来做什么的,那么我们就来说一说怎么在 Trie上构造 AC自动机。
首先,我们看一下条转时的条件,如同 KMP算法一样, AC自动机在匹配时如果当前字符匹配失败,那么利用fail指针进行跳转。由此可知如果跳转,跳转到的串的前缀,必为跳转前的模式串的后缀。由此可知,跳转的新位置的深度一定小于跳之前的节点。所以我们可以利用 bfs在 Trie上面进行 fail指针的求解。
下面,是具体的构造过程(和KMP是一样的)。首先 root节点的fail定义为空,然后每个节点的fail都取决自己的父节点的fail指针,从父节点的fail出发,直到找到存在这个字符为边的节点(向回递归),将他的孩子赋值给寻找节点。如果找不到就指向根节点,具体参照代码:
在前面的Trie建立了fail指针(虚线){其实,这两个图片上的字符应该是在边上的,偷懒了,网上找的图片,有时间回来自己做一下}
4.多串匹配:
既然已经构造好 AC自动机,下面就是写出他的最常见的操作,多串匹配。其实匹配过程很简单,利用Trie匹配字符串,如果失败利用fail指针找到下次匹配的位置即可。具体参照代码:
5.对于 AC自动机的改进:
通过匹配的过程我们可以看出,fail是用来寻找下次跳转的位置的,跳转时的 next一定是为空的。那么我们为什么不用这些 next指针直接指向下一个跳转节点呢,那样的话,匹配时每次去 next指针的对象即可。这个被称作Trie图,具体参照代码:
6.从自动机的角度理解:
自动机可以理解成一个有向图,图中的每个节点都代表一个状态,边上对应的是识别的字符,那么每次识别一个字符就会发生一个状态转向另一个状态。有一个初始状态(root),很多个结束状态(Trie中被标记的点)。
那么我们的匹配过程就是从 root状态出发,利用串的字符寻找下一个状态,每走一步就吃掉一个字符,如果发现到达标记状态则匹配成功。
这是一个自动机的示例,其中箭头指向的是起始状态(S),双圈的代表结束状态(C,D,E,F)
7.时间复杂度分析:
对于Trie的匹配来说时间复杂性为:O(max(L(Pi))L(T))其中L串的长度函数,P是模式串,T是目标串。
对于 AC自动机来说时间复杂性为:O(L(T)+max(L(Pi))+m)气质m是模式串的数量。
对于 Trie 图 来说时间复杂性为:O(L(T))在此的时间复杂性都是指匹配的复杂度。
对于构造的代价是 O(sum(L(Pi)))其中sum是求和函数。
8.题目分析:
下面对于近期所做的 AC自动机的题目加以分类总结
a.模式匹配:这类问题一般都是统计目标串中模式串的个数。下面是oj中的题目编号,和说明:
hdu1686 Oulipo: 寻找模式串的出现次数,可以重复及覆盖,直接求解
hdu2087 剪花布条: 同上
hdu2222 Keywords Search: 同上
hdu2896 病毒侵袭: 同上
hdu3065 病毒侵袭持续中: 同上,不过要注意非法字符直接返回root,否则会RE
hdu3336 Count thestring: 同上上
zoj3228 Searching the String: 同上,不过不允许覆盖,记录每个状态的最晚结束位置即可
zoj3430 Detect the Virus: 同上上,统计很简单,主要是编码有点纠结
b.字符串统计:这类题目一般都是求解某种串个数,可先求解状态转移矩阵然后利用矩阵乘法或 DP求解
poj2778 DNA Sequence: 求解不包含某些子串的串的个数,利用AC自动机构造转移矩阵,然后利用矩阵乘法求解路径个数
hdu2243 考研路茫茫——单词情结:上题的升级版,做法一样,由于长度不定最后要利用快速幂和,有点纠结
zoj1540 Censored!: 题目和上面的类似,不过状态过多不宜使用矩阵乘法,所以利用 DP求解
hdu2825 Wireless Password: 统计关键字不少于k的串的个数,并且每个只用一次,先利用状态压缩 DP统计
c.字符串构造(AC自动机+DP):其实本组和上一组基本相同,不过都是求最优解,所以单独拿出来了,而且没什么共同点
zoj3013 Word Segmenting: 其实这个题目本来是用字典树写的,学了AC自动机之后就优化了一下,求解单词覆盖的最小失败次数
poj3691 DNA repair: 求解将目标串取除某些串的最少操作,改变合法状态时如果对应不同则+1,否则不变;非法则不转移
zoj3545 Rescue the Rabbit: 这就是万恶之源了,AC自动机就是为了他学的,构造最优串,用状态压缩DP记录转移状态,最后求解
hdu2296 Ring: 构造一个串使其权值最大长度最小,而且要字典序最小。有点纠结,DP长度短的优先,然后字典序
hdu3341 Lost's revenge: 传说中的RE神题,由于状态计算错误,导致RE2次,其实就是DP,不过要先将状态分解在拼装
zoj3190 Resource Archiver: 本次学习的收尾题目,传说中的神题,要先构造AC自动机,然后利用最短路优化将问题转化为TSP问题
9.结束语:
AC自动机的总结到此结束,不久后还会更新,如果那里有错还请指出。
最后给出本人的 AC自动机的模板,和上面题目的更加详细的题解和代码,请在本空间搜索即可。
原文: http://blog.csdn.net/mobius_strip/article/details/22549517