引 入 引入 引入
Manachar算法主要是处理字符串中关于回文串的问题的,这没什么好说的。
M a n a c h e r 算 法 Manacher算法 Manacher算法
朴素
求一个字符串中以每个点为中心的最长回文子串,这是 m a n a c h e r manacher manacher 直接解决的问题。
以每个点为中心的回文子串是连续的,它并不像字符串的 b o r d e r border border ,因此可以二分,于是在不知道 m a n a c h e r manacher manacher 的情况下其实可以用字符串哈希+二分,只不过是 O ( n l o g n ) O(nlogn) O(nlogn) 的。
但是 m a n a c h e r manacher manacher 可以 O ( n ) O(n) O(n) 算出,因此在有些专门卡 l o g log log 的题目中 m a n a c h e r manacher manacher 有不可比拟的优越性。
我们不妨设以 i i i 为中心的最长回文子串长度为 F [ i ] ∗ 2 + 1 F[i]*2+1 F[i]∗2+1 , m a n a c h e r manacher manacher 实际上是利用了一个回文字符串内的 F [ ] F[] F[] 的性质。
一个回文字符串,因为它是对称的,因此在仅考虑此字符串的情况下,它的 F F F 数组也是对称的,这便是 m a n a c h e r manacher manacher 算法的核心了吧。
因此,我们考虑从左到右依次计算 F [ i ] F[i] F[i] ,当遍历到一个点 i i i 时,首先 F [ i − 1 ] F[i-1] F[i−1] 肯定已经算出来了。
如果 F [ i − 1 ] = = 0 F[i-1]==0 F[i−1]==0 就直接暴力扩展 i ,否则,如果 F [ i − 2 ] < F [ i − 1 ] − 1 F[i-2] < F[i-1]-1 F[i−2]<F[i−1]−1,说明什么呢?
F [ i ] = F [ i − 2 ] F[i]=F[i-2] F[i]=F[i−2],一定是这样的.
- F [ i ] F[i] F[i] 肯定不能小于 F [ i − 2 ] F[i-2] F[i−2] ,因为这个回文子串以 i − 1 i-1 i−1 为对称中心,两边对称的两点,既然其中 i − 2 i-2 i−2 可以扩大到 F [ i − 2 ] F[i-2] F[i−2],且在 i − 1 i-1 i−1 为中心的大回文串内 ,由于对称性,因此 [ i − F [ i − 2 ] , i + F [ i − 2 ] ] [i-F[i-2],i+F[i-2]] [i−F[i−2],i+F[i−2]] 范围内也一定是个回文串。
- F [ i ] F[i] F[i] 也不能大于 F [ i − 2 ] F[i-2] F[i−2] ,因为以每个点为中心的回文子串是连续的, F [ i ] > F [ i − 2 ] F[i]>F[i-2] F[i]>F[i−2] 意味着 F [ i ] F[i] F[i] 可以为 F [ i − 2 ] + 1 F[i-2]+1 F[i−2]+1,而由于 F [ i − 2 ] < F [ i − 1 ] − 1 F[i-2] < F[i-1]-1 F[i−2]<F[i−1]−1 ,对于 i − 2 i-2 i−2 来说, F [ i − 2 ] + 1 F[i-2]+1 F[i−2]+1 仍然在大回文串范围内,因此 F [ i − 2 ] F[i-2] F[i−2] 肯定就不止这个数了。
然后呢,是不是可以继续利用这个大红串呢?我们还可以考虑 F [ i − 3 ] F[i-3] F[i−3] 是否小于 F [ i − 1 ] − 2 F[i-1]-2 F[i−1]−2 来决定 F [ i + 1 ] F[i+1] F[i+1] ,考虑 F [ i − 4 ] F[i-4] F[i−4] 是否小于 F [ i − 1 ] − 3 F[i-1]-3 F[i−1]−3 来决定 F [ i + 2 ] F[i+2] F[i+2] ……直到超出红框或者:有一个 F [ i − 2 − x ] ≥ F [ i − 1 ] − 1 − x F[i-2-x] ≥ F[i-1]-1-x F[i−2−x]≥F[i−1]−1−x,这时候, F [ i + x ] F[i+x] F[i+x] 至少能扩大到红框边界吧,因为它的对称点都能扩大到红框边界。然后,再把 F [ i + x ] F[i+x] F[i+x] 暴力往外扩。
我们会发现,整个过程中,每次往外扩一定能使前面所有的 i + F [ i ] i+F[i] i+F[i] 的最大值变大,因此,它是线性的。
转化
很明显,对于一个回文字符串而言,并不一定中心是一个字符,也有可能是两个相同的字符的中界,即回文串长度为偶数,因此,一般把原串中每两个字符中间添一个没出现过的字符,于是所有的 F [ i ] ∗ 2 + 1 F[i]*2+1 F[i]∗2+1(包括边界的)都会变成 ( F [ i ] ∗ 2 + 1 ) ∗ 2 + 1 (F[i]*2+1)*2+1 (F[i]∗2+1)∗2+1 ,这样就可以解决长度为偶数的情况。
模板
void Manacher(char *s,int *F,int n) {
// 已是转化后的 S
F[1] = 0;
for(int i = 2;i <= n;i ++) {
int j = i;
while(j < i-1+F[i-1] && j+F[i-1-(j-i+1)] < i-1+F[i-1]) F[j] = F[i-1-(j-i+1)],j ++;
F[j] = max(0,i-1+F[i-1] - j);
while(j+F[j] < n && j-F[j] > 1 && s[j+F[j]+1] == s[j-F[j]-1]) F[j] ++;
i = j;
}
return ;
}