关于回文树的理解
2018-02-22 20:43
239 查看
前言
这段时间搞字符串上了瘾?看起来是的
那就继续搞吧
Part1一些名词
回文串
不想解释什么意思回文子串
一个串的子串,它是回文串,那么它就是回文子串最长回文后缀
对于一个长度小于自己的后缀,如果它是回文串,并且不存在比它更长的回文后缀,那么它就是最长回文后缀最长回文前缀
基本和上面一样Part2 回文树的形态
长成啥样啊?
我们很容易知道,回文串有两种,一种长度是奇数,一种长度是偶数而在回文树上走,我们肯定不是一次只在后面添加一个字符
显然是在前后各添加一个字符
所以我们不难得出一点,如果串可以变成另外一个回文串
那么它的长度一定加上了一个偶数
所以在回文树上,为了区分这两种不同的回文串
所以回文树相当于一个森林
有两棵树,一棵的代表长度为奇数的回文串,另一棵代表长度为偶数的回文串
就像后缀自动机,Trie树,AC自动机这些东西一样
每一个节点代表的都是一个(些)串
回文树的每个节点也是代表着一个串
对于每个点的转移,比如说
对于某个点代表的回文串”aba”
假如它有一个’c’的转移
那么,”aba”就会指向一个代表着”cabac”的串
同样的,类似于AC自动机有fail,后缀自动机有parent
当失配的时候回文树也有fail向上跳
那么,我们来考虑一些这个东西是什么?
假设当前加入的位置是r
如果之前已经匹配出了一个回文Sl..r−1
那么,如果有Sl−1=Sr就没有失配
如果失配了,因为r位置是不能变动的
所以挪动的只有l位置
而Sl..r显然也要是一个回文串
所以l挪动到的位置就是Sl..r−1的最长回文后缀的开始位置
一些小小的结论
综上所述,我们知道了两点:1.对于回文树上的两个节点,如果存在字符c的连边,那么,就会从串x,变成cxc
2.对于回文树上的失配(fail)指针,指向这个点所代表的字符串的最长回文后缀所在的节点
一些小小的证明
接下来,我们还可以知道几点1.对于任何一个串S,它的本质不同的回文串的个数不会超过|S|个
2.如果在串S后面加入一个字符,新增的本质不同的回文串的个数不会超过1个
怎么证明?
利用数学归纳法来证明
当|S|=1时,显然成立
如果我们知道|S|=x−1时成立,现在插入x位置,字符为c
如果以x位置结尾出现了两个新的本质不同的回文串
假设较长的从l1开始,较短的从l2开始
因为|Sl1..r|>|Sl2..r|
又根据回文串对称的性质
所以Sl2..r在Sl1..l1+r−l2必定出现过
所以不存在两个本质不同的回文串
所以最多新增一个本质不同的回文串
所以到x位置出现的本质不同的回文串的个数最多为x个
同时,我们也证明了每次插入一个新的字符,最多增加一个本质不同的回文子串
Part3 回文树的构造
看完了上面,应该就知道了回文树上的东西代表着什么我们的构造采用增量法,也就是类似于后缀自动机的extend
假设前面已经构造出了1..x−1的回文树,现在要加入第x个字符c
因为要接在x−1的后面,我们又知道最多一个产生一个新的本质不同的回文串
也就是S1..x−1中,最长的某个回文后缀Sl..x−1,
同时能够满足Sl−1=Sx
因为只需要不停地寻找最长回文后缀
根据回文树上的fail指针的含义
我们很容易知道知道,
只需要从上一个位置添加完之后的最后一个位置
(也就是以x−1为结束位置的最长回文子串)
所代表的节点开始,沿着fail一路上跳
检查是否满足Sl−1=Sx就行了
假设这样找到的一个位置是p
不难证明这个位置p一定存在(为啥?长度为1的回文串呀)
如果p.son[c]也就是连边c已经存在
那就什么都不用干,因为这个回文子串已经存在过
不需要重新建边
否则,重新建一个点表示这个回文子串,假设点是np吧
然后p.son[c]=np
现在我们要找np的fail啦
因为要找的是最长回文后缀,不能是自己
所以令k=p.fail
然后就像前面一样的,找到第一个满足S[lk−1]=S[n]的点
让k沿着fail向上跳
然后np.fail=k,表示找到啦
这样,我们的回文树就利用增量法构建出来啦
当然,两棵树的根节点的长度分别是−1和0
然后为0的根节点的fail连向−1的根节点
−1个根节点的fail也连向自己
为啥?自己想
初始情况下的last=0,tot=1(代表什么可以参考程序)
这是一棵回文树
struct Palindromic_Tree { struct Node { int son[26]; int ff,len; }t[MAX]; int last,tot; void init() { t[++tot].len=-1; t[0].ff=t[1].ff=1; } void extend(int c,int n) { int p=last; while(s[n-t[p].len-1]!=s )p=t[p].ff; if(!t[p].son[c]) { int v=++tot,k=t[p].ff; t[v].len=t[p].len+2; while(s[n-t[k].len-1]!=s )k=t[k].ff; t[v].ff=t[k].son[c]; t[p].son[c]=v; } last=t[p].son[c]; size[t[p].son[c]]++; } };
Part4 后记
这篇博客十分简短因此肯定有很多很多不严谨的地方
更加详细的请参考
国家集训队2017年的论文
当然了,这些东西也只是我自己的理解
而回文树也有很多很神奇的用法,
等我做了一些题之后我会再回来写的。
相关文章推荐
- 关于回文树的理解
- 深入理解Java特性:关于继承的使用思考
- 关于两个程序的基本框架的肤浅理解
- 关于c#中static的一些理解
- 关于drools在实战中到底怎么用的理解
- 关于java代码中的异常理解
- 关于SCN的理解
- StoryBoard 关于Segue ----prepareForSegue:sender: 理解
- 关于int main(int argc,char*argv[])的理解
- storm关于fieldsGrouping的理解
- 当数组元素是应用类型的时候,关于内存分配的理解误区
- 关于 Linux 中 inode 的理解
- JavaScript中有关于浏览器坐标理解
- 关于对unity中协程运用的理解
- 关于TP字段field的一些理解
- 关于多核DSP C6678共享存储器问题的理解
- 关于DatePicker的理解(显示当前日期和时间)
- 关于对DOM的childNodes的理解
- 关于对ARM处理器中“8位位图”的理解
- 关于:清除并发请求和(或)管理器数据 请求的理解