关于 manacher

博客园同步

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

给定一个长度为 n n n 的串 s s s,求其最长回文子串。 n ≤ 1 0 7 n \leq 10^7 n107.

大体分析

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

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

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

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

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

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

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

hw ( s i − 1 ⋯ j + 1 ) = hw ( s i ⋯ j ) ∣ ( s i − 1 = = s j + 1 ) \text{hw}(s_{i-1 \cdots j+1}) = \text{hw}(s_{i \cdots j}) | (s_{i-1} == s_{j+1}) hw(si1j+1)=hw(sij)(si1==sj+1)

∣ | 就是位运算中的 ∣ | 符号。

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

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

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

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

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

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

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

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

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

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

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

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

$\texttt{abbcbbc} \rightarrow $ # a \texttt{a} a# b \texttt{b} b# b \texttt{b} b# c \texttt{c} c# b \texttt{b} b#b# c \texttt{c} c#

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

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

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

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

质的飞跃即将到来了。

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

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

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

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

另外 i ≤ r i \leq r ir 的情况比较难解决。

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

于是是否意味着 d i = d j d_i = d_j di=dj 呢?不是的。

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

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

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

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

时间复杂度证明

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

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

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

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

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值