回文树引发的思考(一)

最近在做一些回文树、回文串的题目,对于这类题以及题目会涉及到的算法、额外的算法,有一些思考,特此记录下来。

回文串的题目,除了用回文树去做,还可以用强大、牛掰的后缀数组去做,无论哪种方法,关键都是去重,也就是把每个长的不一样的回文串提取出来,注意这里区分两个回文串的方法是长的一不一样,而不是所在母串里的位置是否相同。

然而用后缀数组去重,方法显得非常麻烦,用回文树去重会比较简单,但我们在做回文串的题目,有时需要用到额外的方法,这些方法不是回文树能解决的,这时又需要回来寻求后缀数组的帮忙。

举以下几个例子来说明,有些题目会有原题链接,有些题目是我现想的。(如果没有特殊说明,字符串长度都假设为10万,即不允许n^2的算法通过

一,求一个字符串每个前缀本质不同回文串的个数

具体题目看我博客的链接吧,里面也有原题的链接:http://blog.csdn.net/uestc_peterpan/article/details/49514769

回文树,跑一遍,总节点数减2的值表示的就是该字符串有多少个本质不同的回文串,是的,就是那么简洁、方便,而求每个前缀本质不同回文串的个数,方法也是类似的,相信不必说明了。回文树,起初它有两个根,一个是奇数串的根,一个是偶数串的根,奇数串的根是0节点,偶数串的根是1节点,0节点的fail指向1节点,1节点的fail指向0节点。(这里要提到一点,我回文树的方法是在hdu的yyn博客上学习的,百度一下csdn,poursoul就知道了,在他的博文里提到,1节点的fail指向的是空,但我不那么认为,实际上,1节点的fail,应该指向的是0节点,我这里有例子可以支持我的说法,就暂且不放出来了,不是什么大问题,过后如果要写一篇回文树介绍的博文的话,再特别提到吧)。

再提到一点,一个长度为n的字符串,它所拥有的本质不同的回文串个数,最多只有n个。关于这个问题和队友有讨论过,当时我还不了解回文树算法,队友说他可以有比如长度为10但找到15个不同的回文串的例子,实际上这是不可能的,从回文树就可以看出,每次加入一个字符最多添加一个节点,而总节点数减2,又表示本质不同回文串的个数,长度为n的字符串,最多只有n个。

二、将一个字符串所有本质不同的回文串字典序排序,只用输出每个字符串所在的开头位置和长度即可

这个问题,单单用回文树就不是那么好解决了,但是回文树可以把每个不同的回文串都表示出来,即每个不同的节点都表示不同的回文串,可以记录每个回文串的长度、在母串的最早起始位置、它内部所含有的回文串的个数、本质不同回文串的个数等等。有了这些信息,再用后缀数组的方法去做字典序排序,我想,就不会很难了。

我们先跑一遍回文树,得到每个回文串,它们的长度以及在母串中的起始位置(如果有多个起始位置,随便记录一个即可,记录最早的起始位置也可以),然后我们跑一发后缀数组,再得到height数组,bool cmp函数里写明,对于两个回文串,找到它们在height数组上的位置p1和p2,得到它们lcp(最长公共前缀)的长度L,好的,得到它们两个串的长度l1, l2,如果 L < min(l1, l2),那么return p1 < p2,否则return l1 < l2;(这种表示方法不难理解吧,即如果 L < min(l1, l2) ,那么哪个串在height上越前,哪个串字典序越小,否则哪个串越短,哪个串字典序越小。

三、给个字符串s,长度10万,进行10万次询问,每次给一个串s1,保证s1是回文串,问s1在s的一个前缀pres里出现的次数

(这里提到一个问题,给十万次s1,有可能输入的复杂度都很高,所以给出s1的一种方法可以变为,只给出s1在s的起始位置和长度,或者起始位置和末尾位置。当然如果一定要给出s1的话,最好要保证10万次的s1总长不要超过10万,50万多少的,再得到每个s1在s的起始位置和长度也是有相应方法的,这里就不多说了

这个问题,提出来,我觉得挺叼的。它和问题一一样,同样涉及到了去重、统计、相同串可能出现在不同位置不好处理等等问题。

是的,可以用height数组+主席树的方法来解决,我们跑一发后缀数组,再得到height数组,对于后缀都建立一棵线段树,实际上是建立一棵主席树,在height数组从上到下建立主席树。线段树上 T(i) 表示的是第 i 个后缀所在母串的起始位置。对于每个询问,我们可以得到s1所在的后缀所在的height位置p,以及它的长度l,上下二分得到两个位置p1,p2。p1到p2的所有后缀都含有当前的回文串s1,并且其它后缀都不含有,假设s1在母串的起始位置为t1,那么问题就很简单啦,问题就转换为,height数组上,p1到p2这段连续的区间的所有后缀里,在母串起始位置 <= t1的串有多少个,这个用建立好的主席树去解决即可,每次询问相当于转换为区间第k大类的问题。

这当然是一种方法,复杂度也是nlogn的,常数会比较大,码量不会太小,因为涉及到“上下二分得到两个位置p1,p2“这个方法,其实我不是很推荐这个方法,因为代码量比较大,如果写的不熟练,在二分那里很有可能会被卡住。反正,代码量越大,就越容易出错,还不容易查错。

并且,这里再将问题的难度加大一下,如果问题不是把s串一次性的给出来,而是边询问边加长s串的话,那上述的方法就无法解决了,因为后缀数组必须要得到一个完整的字符串。(虽然也可以离线做,但如果强制在线的话,就不行了

那怎么做呢?既然是回文串问题,可以去考虑回文树来做,并且后来会发现,回文树来做这道题,比较简洁容易,代码量小,同时可以解决动态的版本。

对于s串,我们用来不断地更新回文树,对于每次的询问,给出的s1,我们可以用一些方法快速的得到它所在回文树的节点 t1,假设当前节点是cur。好的,对于回文树,每个节点都有一个fail指针,对吧?那么我们根据fail指针来建树,也可以得到一个新的fail树,是回文树上的fail树,我们假设这个fail树为F树。每次得到一个新的s字符si的时候,我们更新回文树,得到当前的节点为cur,我们就在F树上,从根到cur节点路径上的所有节点权值+1,对于每次查询的s1,我们得到它在F树上的节点 t1(实际上回文树和fail树节点都是一致的,只不过边不一致),那么t1节点的权值,就是这个s1所在当前前缀所出现的次数。

具体为什么,和回文树统计每个本质不同回文串出现次数的方法以及回文树的结构有关系,多了解回文树就可以明白,那么第三个问题就可以转换为一个,不断建树,不断增加一个节点到根的路径的权值,不断查询一个节点的权值的题目,转化后的题目,用树形转线性的方法配合树状数组就可以解决了(如果是强制在线的话,可能还需要splay,额,再见)。代码量相对第一种方法会小很多,而且比较好写。

暂时就写那么多了,可能文章中会出现一些问题,比如语句不通顺,因为我没有去审核,比如算法描述不正确或者无法实现,毕竟后两题我没有出现成的数据,没有写暴力对跑或者第二种方法,没有写标程= =。现在没有那么多时间嘛

你可能感兴趣的:(回文树)