算法介绍:
个人理解:
Manacher算法可以在线性时间复杂度(O(2n))内求出一个字符串的最长回文子串。同时可以处理出以每个元素为中心的最长回文子串。
由于它把原字符串s每个元素之间加了'#"(包括首尾),所以总共加了n+1个'#',所以转换后的字符串 t 总共有2*n+1个元素,元素个数一定为奇数,所以就不需要再分奇偶考虑,这个是巧妙点之一。
Manacher关键在于求解length数组,length[i]其含义是i到以 i 为中心的最长回文子串右端点的距离。比如说以 i 为中心的最长回文子串是[l,r],length[i] = r-i 。length数组是对t字符串求解的。但它有一个性质:length[i]-1就是s字符串以i为中心的最长回文子串的长度。
现在有个问题,如果t[i]=='#',那length[i]代表着什么?这就是刚才上面讲到的奇偶的问题。如果一个回文子串为奇数,比如aabaa,那么对应它的length[i]=6,且t[i]='b'。但如果一个回文子串为偶数,比如abba,那么对应它的length[i]=5,且t[i]='#'(相当于以'#'为中心)。
接下去最重要的就是length[ ]的求解:
首先从左往右依次计算length[i],当计算length[i]时,length[j](0<=j<i)已经计算完成。设P为之前计算中最长回文子串的右端点的最大值,并且设该回文子串的中心为po。
第一种情况:i<=P
这里还要再分2种情况:
第一种:i+length[i]<P(如下图),那么i关于po的对称点j其实是一样的(因为对称嘛),所以length[i]=length[2*po-i] 。
第二种:i+length[i]>=P(如下图),那么i到P的这部分肯定是和j到2po-P的这部分一样的,但超出部分是可能不一样的,所以要遍历超出部分,直到发生失配,从而更新P和对应的po以及length[i]。
第二种情况:i>P
如果i比P还要大,说明对于中点为i的回文串还一点都没有匹配,这个时候,就只能老老实实地一个一个匹配了,匹配完成后要更新P的位置和对应的po以及length[i]。
因为算法只有遇到还没有匹配的位置时才进行匹配,已经匹配过的位置不再进行匹配,所以对于T字符串中的每一个位置,只进行一次匹配,所以Manacher算法的总体时间复杂度为O(n),其中n为t字符串的长度,由于t的长度事实上是s的两倍,所以时间复杂度依然是线性的。
Code:
纯净版本:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 110000+10;
const ll mod = 1e9+7;
char s[MAX];
char t[MAX<<1];
int length[MAX<<1];
int trans(int len)
{
int cnt=1;
t[0]='@';
for(int i=0;i<len;i++){
t[cnt++]='#';
t[cnt++]=s[i];
}
t[cnt]='#';
t[cnt+1]='$';
t[cnt+2]='\0';
return cnt;
}
int Manacher(int len)
{
int ans=0,po=0,Max=0;
for(int i=1;i<=len;i++){
if(i<Max){
length[i]=min(length[2*po-i],Max-i);
}
else{
length[i]=1;
}
while(t[i+length[i]]==t[i-length[i]]) length[i]++;
if(i+length[i]>Max){
Max=i+length[i];
po=i;
}
ans=max(ans,length[i]);
}
return ans-1;
}
int main()
{
while(scanf("%s",s)!=EOF)
{
int len = strlen(s);
int tlen = trans(len);
int ans = Manacher(tlen);
printf("%d\n",ans);
}
return 0;
}
注释版本:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAX = 110000+10;
const ll mod = 1e9+7;
char s[MAX]; //原字符串
char t[MAX<<1]; //变换后的字符串(可以奇偶同时考虑)
int length[MAX<<1]; //length[i]-1就是该回文子串(以 i 为中心)在原字符串S中的长度
//转换原始串
int trans(int len)
{
int cnt=1;
t[0]='@'; //字符串开头增加一个特殊字符,防止越界
for(int i=0;i<len;i++){
t[cnt++]='#';
t[cnt++]=s[i];
}
t[cnt]='#';
t[cnt+1]='$'; //字符串结尾加一个字符,防止越界
t[cnt+2]='\0';
return cnt; //返回转换后字符串t的长度
}
//Manacher算法计算过程
int Manacher(int len) //这个len是t[ ]的长度
{
int ans=0,po=0,Max=0; //Max即为当前计算回文串最右边字符的最大值
for(int i=1;i<=len;i++){
if(i<Max){
length[i]=min(length[2*po-i],Max-i); //在length[j]和Max-i中取较小值
}
else{
length[i]=1; //如果i>=Max,要从头开始匹配
}
while(t[i+length[i]]==t[i-length[i]]) length[i]++;
//若新计算的回文串右端点位置大于Max,要更新po和Max的值
if(i+length[i]>Max){
Max=i+length[i];
po=i;
}
ans=max(ans,length[i]);
}
return ans-1;//返回length[i]中的最大值-1即为原串的最长回文子串长度
}
int main()
{
while(scanf("%s",s)!=EOF)
{
int len = strlen(s);
int tlen = trans(len);
int ans = Manacher(tlen);
printf("%d\n",ans);
}
return 0;
}