manacher算法详解
Part 0. 引子
自己接触 OI 已经很长时间了,但是字符串仍然处于门外汉的尴尬境地。
于是,我决定狂补字符串。
Manacher(中文谐音马拉车),是解决最长回文串的一种高效算法。他能够在 的时间内解决这个问题,同时代码很简洁。
Part 1. 先聊聊暴力
暴力方式就是所谓的“中心扩展法”。说白了就是枚举每一个字符,然后往左右两边分别扩展,确定以这个字符为中心的回文有多长。
很明显,这个算法的时间复杂度是 的。当然了,如果这个字符串长这个样子:
1 | qwertyuiopasdfghjklzxcvbnm |
代码在进行计算的时候,啥也扩展不出去,每一次刚往外扩展了一格,就发现无法匹配。结果就是整个代码的运行几乎是 的。
但是如果这个字符串长这个熊样呢?
1 | aaaaaaaaaaaaaaaaaaaaaaaaaaa |
这个字符串和上面那个完全一样长,但是我们每一次都要扩展到两端才能停止。这就是个极为低效的 了。
出题人肯定会用第二种数据,想办法卡掉暴力中心扩展法的。
Part . 在此之前
对于字符串,我们进行一个小小的预处理:本来的字符串中,既有长度为奇数的回文,也有长度为偶数的回文。长度为偶数的回文判断起来要麻烦一些,因为他没有一个确切的中心,所以我们不得不两个两个地枚举偶数回文的中心。
有没有方法简化呢?很简单,我们把回文变成这样就行了。
1 | 原来: |
具体插入了什么字符不重要,重要的是这样一来我们本来的偶回文就变成了奇回文,中心是一个插入的字符。
因此我们接下来只讨论奇回文的情况。
Part 2. 分析暴力
在暴力的时候,我们会发现重复的一个字符被扫描了许多便。比如说,在我们对于aaaaaaaaaaaaaaaaaaaaaaaaaaa
进行操作的时候,最头上的那个a
被扫描了13遍。
但是我们每一次的中心是不一样的啊!不能简单地记忆化,因为我怎么知道下一次是谁扫到我了?
不过,仍然有解决方法。
Part 3. 回文的镜像也是回文
虽然我们不能直接记忆化,但是我们来考虑一个特殊情况:
当前我们要求 位置的回文串长度,此时我们有一点 ,是整个字符串中已经扫描过(注意,由于中心扩展法是按照中心从左往右遍历的,所以最靠右的可能并不是最右边的中心) 的最靠右的字符, 是扫描到他的中心。此时,令 为 关于 对称的位置,以他为中心的回文长度是已经知道的,那么我们就可以同理得到, 的回文也是这么长。
那还算啥啊。
不过这是特例。还有其他情况。
考虑这种情况。 我们确实知道了,但是他超出了 回文的范围。此时我们不能继续利用刚才的方式,正确的做法是这样:我们可以确定,图中所示的 所在的标红区域是回文。后面的我们直接暴力扩展即可。扩展完成后更新 和 。
设以 为中心的最长回文半径为 ,在实际操作时,两种情况分别如下:
- 情况一:
- 情况二:,暴力扩展
其实我们可以把上面两种情况取个 ,然后直接暴力扩展,第一种情况下可以证明整个过程不会再次扩展任何值。
当然了,还有第三种情况,就是 ,这就没救了,只能暴力扩展。
Part 4. 复杂度分析
上面这个东西好像复杂度也是 的啊?
不,我们会发现,只要有 ,我们都不会再进行计算,直接调用现成的值,当 的时候,我们确实会扩展,但是扩展以后我们的 也随之更新了啊!因此这个算法的复杂度是 。
Part 5. Code for P3805
1 |
|