zoukankan      html  css  js  c++  java
  • 关于 manacher

    CSDN同步

    这一节我们试图解决这样一个问题:

    给定一个长度为 (n) 的串 (s),求其最长回文子串。(n leq 10^7).

    大体分析

    貌似有 (n^2) 个供选择的子串,于是似乎很难有线性的做法。

    算法一 暴力 (mathcal{O}(n^3))

    比较屑。把 (n^2) 个子串枚举出来,一个个验证。

    算法二 动态规划 (mathcal{O}(n^2))

    很显然不能考虑去枚举子串了。

    考虑快速判断回文。假设我们已经知道 (s_{i cdots j})回文性(即是否回文),如何知道 (s_{i-1 cdots j+1}) 的回文性?

    很显然。如果用 ( ext{hw}(s) = 0 / 1) 表示其回文性,(1) 为回文的话:

    [ ext{hw}(s_{i-1 cdots j+1}) = ext{hw}(s_{i cdots j}) | (s_{i-1} == s_{j+1}) ]

    (|) 就是位运算中的 (|) 符号。

    很显然,只有 (s_{i cdots j}) 回文且多出的一位可以匹配成功的情况下,(s_{i-1 cdots j+1}) 才是回文。

    这样我们可以得到一个有些玄学的 “( ext{dp})” 做法。

    考虑求出所有的 ( ext{hw}) 值,利用刚才的动态规划转移方程,把这玩意儿搞成 区间动规,于是可以 (mathcal{O}(n^2)) 完事了。

    算法三 朴素做法 (mathcal{O}(n^2))

    基于算法二,考虑一个不基于动态规划,而基于暴力的做法。

    比如 abbcbbc 中,b , bcb , bbcbb 均为回文子串,其特点在于 中心一样

    于是我们可以枚举中心字符,然后不断向两边扩展,这个思路也很好理解。

    但是对于偶数个字符的问题,貌似解决不了。

    一个方法是,枚举完奇数之后,枚举 (s_i = s_{i+1}) 的所有 (i),再向两边扩展。

    这个方法比较易懂一些。但是下面我们要介绍一个,更贴合 ( ext{manacher}) 特点的解决方案。

    考虑本来的串是 abbcbbc,对于偶数回文子串 bb,考虑只枚举一个中心就做完的方案。

    也就是这样操作:将原串每两个字符的间隔内加上一个新的,原串中没有的字符,一般用 # 表示。即

    $ exttt{abbcbbc} ightarrow $ #( exttt{a})#( exttt{b})#( exttt{b})#( exttt{c})#( exttt{b})#b#( exttt{c})#

    这样你会发现一件事。对于原来就有的字符,枚举其中心的做法没有问题,只不过要对长度进行处理罢了,这样解决了奇数回文的情况;而对 # 的中心扩展方案,则是解决了偶数回文的情况。

    这样子串长度翻了一倍,但循环一遍即可解决,较为简单。

    于是,我们管这个算法叫 “朴素算法”。虽然 (mathcal{O}(n^2)) 的复杂度不优于上述动态规划的复杂度,但在思维上已经跨出了一大步。

    算法四 ( ext{manacher} space mathcal{O}(n))

    质的飞跃即将到来了。

    先着手解决奇数串。因为上述添加 # 的操作,偶数串用同样的代码即可求出。

    (d) 表示以 (i) 为中心的奇数串的个数,方便记忆。

    下面考虑 (d_{i-1} ightarrow d_i) 怎么做。为方便,我们令 (l,r) ,其中 (r) 是当前回文子串最右侧的位置,(l) 为其对应的子串左端点。

    如果 (i>r),我们只能调用朴素算法。因为与前面的元素无法形成联系。

    另外 (i leq r) 的情况比较难解决。

    由于 (i) 包含在 (s_{l cdots r}) 的回文串中,则必然存在 (s_i = s_{l+r-i}),并且 在该范围内,以 (l+r-i) 为中心的回文子串必然也有与其对应的以 (i) 为中心的回文子串。注意 “在该范围内”。此时令 (j = l+r-i).

    于是是否意味着 (d_i = d_j) 呢?不是的。

    因为很有可能,以 (j) 为中心的最长回文子串的左端点超出了 (l),此时就不一定会产生对应关系。也就是 (j - d_j < l) 的时候,需要分类讨论。

    考虑这个时候,我们用朴素算法求解。也就是暴力拓展。

    无论什么情况,不要忘记维护 (l,r) 的值。

    算法讲完了。可你觉得这是对的么?

    时间复杂度证明

    简单地证明,( ext{manacher}) 算法的时间复杂度是线性 (mathcal{O}(n)) 的。

    因为很显然,(r) 只会不断变大(或不变)。每一次朴素做法都是将 (r) 不断扩大,再至多从 (r) 开始继续暴力搜。

    这样 (r uparrow) 的复杂度是 (mathcal{O}(n)) 的,往左扫的次数和往右扫一样,也是 (mathcal{O}(n)) 的,其余部分也都是线性的。

    这样最终的复杂度就是 (mathcal{O}(n)) 了。

    于是我们成功的在 (mathcal{O}(n)) 的时间内解决了最长回文子串问题。

    简易的代码胜过复杂的说教。
  • 相关阅读:
    noi 2011 noi嘉年华 动态规划
    最小乘积生成树
    noi 2009 二叉查找树 动态规划
    noi 2010 超级钢琴 划分树
    noi 2011 阿狸的打字机 AC自动机
    noi 2009 变换序列 贪心
    poj 3659 Cell Phone Network 动态规划
    noi 2010 航空管制 贪心
    IDEA14下配置SVN
    在SpringMVC框架下建立Web项目时web.xml到底该写些什么呢?
  • 原文地址:https://www.cnblogs.com/bifanwen/p/14327157.html
Copyright © 2011-2022 走看看