关于 manacher
这一节我们试图解决这样一个问题:
给定一个长度为 \(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}\) 的回文性?
很显然。如果用 \(\text{hw}(s) = 0 / 1\) 表示其回文性,\(1\) 为回文的话:
\(|\) 就是位运算中的 \(|\) 符号。
很显然,只有 \(s_{i \cdots j}\) 回文且多出的一位可以匹配成功的情况下,\(s_{i-1 \cdots j+1}\) 才是回文。
这样我们可以得到一个有些玄学的 “\(\text{dp}\)” 做法。
考虑求出所有的 \(\text{hw}\) 值,利用刚才的动态规划转移方程,把这玩意儿搞成 区间动规,于是可以 \(\mathcal{O}(n^2)\) 完事了。
算法三 朴素做法 \(\mathcal{O}(n^2)\)
基于算法二,考虑一个不基于动态规划,而基于暴力的做法。
比如 abbcbbc 中,b , bcb , bbcbb 均为回文子串,其特点在于 中心一样。
于是我们可以枚举中心字符,然后不断向两边扩展,这个思路也很好理解。
但是对于偶数个字符的问题,貌似解决不了。
一个方法是,枚举完奇数之后,枚举 \(s_i = s_{i+1}\) 的所有 \(i\),再向两边扩展。
这个方法比较易懂一些。但是下面我们要介绍一个,更贴合 \(\text{manacher}\) 特点的解决方案。
考虑本来的串是 abbcbbc,对于偶数回文子串 bb,考虑只枚举一个中心就做完的方案。
也就是这样操作:将原串每两个字符的间隔内加上一个新的,原串中没有的字符,一般用 # 表示。即
$\texttt{abbcbbc} \rightarrow $ #\(\texttt{a}\)#\(\texttt{b}\)#\(\texttt{b}\)#\(\texttt{c}\)#\(\texttt{b}\)#b#\(\texttt{c}\)#
这样你会发现一件事。对于原来就有的字符,枚举其中心的做法没有问题,只不过要对长度进行处理罢了,这样解决了奇数回文的情况;而对 # 的中心扩展方案,则是解决了偶数回文的情况。
这样子串长度翻了一倍,但循环一遍即可解决,较为简单。
于是,我们管这个算法叫 “朴素算法”。虽然 \(\mathcal{O}(n^2)\) 的复杂度不优于上述动态规划的复杂度,但在思维上已经跨出了一大步。
算法四 \(\text{manacher} \space \mathcal{O}(n)\)
质的飞跃即将到来了。
先着手解决奇数串。因为上述添加 # 的操作,偶数串用同样的代码即可求出。
令 \(d\) 表示以 \(i\) 为中心的奇数串的个数,方便记忆。
下面考虑 \(d_{i-1} \rightarrow 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\) 的值。
算法讲完了。可你觉得这是对的么?
时间复杂度证明
简单地证明,\(\text{manacher}\) 算法的时间复杂度是线性 \(\mathcal{O}(n)\) 的。
因为很显然,\(r\) 只会不断变大(或不变)。每一次朴素做法都是将 \(r\) 不断扩大,再至多从 \(r\) 开始继续暴力搜。
这样 \(r \uparrow\) 的复杂度是 \(\mathcal{O}(n)\) 的,往左扫的次数和往右扫一样,也是 \(\mathcal{O}(n)\) 的,其余部分也都是线性的。
这样最终的复杂度就是 \(\mathcal{O}(n)\) 了。
于是我们成功的在 \(\mathcal{O}(n)\) 的时间内解决了最长回文子串问题。

浙公网安备 33010602011771号