Able was I ere I saw Elba. ----Napoléon Bonaparte(拿破仑)
一、回文串&回文子串
这个很好理解。
如果一个字符串正着读和反着读是一样的,那它就是回文串。 eg. abba ;
如果一个字符串 S 的子串 SS 为回文串,那么 SS 即为 S 的回文子串;若 SSS 为 S 的回文子串中最长的一个,那么我们称 SSS 为 S 的最长回文子串。
二、Manacher算法
如何找到一个字符串的最长回文子串呢?
我们很容易想到一个 O(n2) 的方法,即:从每个字符开始向两边爆搜。但显然,这个方法效率太低下了。
如何快速求出答案,这就是 Manacher算法的事了。
Ⅰ. 奇回文&偶回文
字面意思,不再赘述。
如果同时存在奇回文和偶回文,那么处理起来会比较的繁琐,下面就是Manacher 一个很巧妙的方法了:在字符串收尾,即各字符间插入一个特殊的字符(指没有出现过的字符),例如:
aba ----> #a#b#a# abba ----> #a#b#b#a#
这样,所有的回文串就都成奇回文了。
inline int Pre() { S[0]='@',S[1]='#'; int j(1); for(register int i=0,len=IP.size();i<len;++i) S[++j]=IP[i],S[++j]='#'; S[++j]='\0';return j; }预处理
Ⅱ. 最长回文半径
令一个回文串中最左或最右位置的字符与其对称轴的距离称为 " 回文半径 ",令 R [ i ] 表示字符 i 的最长回文半径。
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
S | @ | a | # | b | # | b | # | a | # | b | $ |
R[i] | 边界 | 1 | 1 | 2 | 4 | 2 | 1 | 4 | 1 | 2 | 边界 |
显然,R [ i ] - 1 即为以 i 为中心的最长回文子串的长度。
那么,我们要求的最长回文子串,就成了 max { R [ i ] - 1 } 。
如何快速求出 R [ ] ???
Ⅲ. Manacher
从左往右依次讨论。
设 Max 为已讨论的子串中能达到的右端最大位置,P 为提供当前 Max 的字符位置。如下表:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
Max的对称点 | P | Max |
接下来讨论的位置 i 可以分为两种情况:小于等于 Max ,大于 Max:
1. 小于等于 Max :
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
Max的对称点 | j(i的对称点) | P | i | Max |
由于 i 和 j 对称 ,所以求出 i 的对称点 j ,简直是 "轻易而举" 。
不难发现现在有可以分为两种情况讨论:
( 1 ) . 以 j 为对称轴的回文串比较短:那么可以直接令 R [ i ] = R [ j ] ;
( 2 ) . 以 j 为对称轴的回文串比较长:此时,我们只能确定不超过 Max 的部分的情况,对于 Max 以外的,我们需要以 i 为中心开始往两侧拓展,直到两侧不同,同时更新 p 和 Max。
2. 大于 Max : 这种情况很好处理,直接拓展就可以了(当然,要同时更新 p 和 Max)
Ⅳ. 代码
#pragma GCC optimize(2) #pragma GCC optimize(3) #include<bits/stdc++.h> using namespace std; char S[50000005]; int m,R[50000005]; string IP; inline int Pre() { S[0]='@',S[1]='#'; int j(1); for(register int i=0,len=IP.size();i<len;++i) S[++j]=IP[i],S[++j]='#'; S[++j]='\0';return j; } inline int Manacher() { m=Pre(); int RET(-1),x(0),Max(0); for(register int i=1;i<m;++i) { if(i<Max) R[i]=min(R[2*x-i],Max-i+1); else R[i]=1; while(S[i-R[i]]==S[i+R[i]]) ++R[i]; if(Max<i+R[i]-1) x=i,Max=i+R[i]-1; RET=max(RET,R[i]-1); } return RET; } int main() { ios::sync_with_stdio(false); cin>>IP,cout<<Manacher(); return 0; }Manacher
Ⅴ. 时间复杂度
由于Max是不断向右拓展的,最多拓展 n 次,不难得出 马拉车 的时间复杂度是线性的,即 O( n )。