【马拉车Manacher算法】
引入:
计算字符串的最长回文字串最朴素的算法就是枚举字符串的每一个子串并判断这个子串是否为回文串,这个算法的时间复杂度为O(n^3),显然无法令人满意。稍微优化的算法是枚举回文串的中点,这里要分为两种情况,一种是回文串长度是奇数的情况,另一种是回文串长度是偶数的情况,枚举中点再判断是否是回文串,这样能把算法的时间复杂度降为O(n^2),但是当n比较大的时候仍然无法令人满意。而Manacher算法可以在O(n)时间复杂度内求出一个字符串的最长回文字串,达到了理论上的下界。
实现:
Manacher算法提供了一种巧妙的办法,将长度为奇数的回文串和长度为偶数的回文串一起考虑,具体做法是,在原字符串的每个相邻两个字符中间插入一个分隔符,同时在首尾也要添加一个分隔符,分隔符的要求是不在原串中出现。
用一个数组Len[i]表示以字符T[i]为中心的最长回文字串的最右字符到T[i]的长度,比如以T[i]为中心的最长回文字串是T[l,r],那么Len[i]=r-i+1。Len数组有一个性质,那就是Len[i]-1就是该回文子串在原字符串S中的长度。
Len数组的计算:设mx为以字符T[i]为中心的最长回文字串的最右字符的位置,pos为字符T[i]的位置i,i从前往后匹配。存在两种情况:①当i<mx时,我们把以pos为对称中心的与i对称的点设为j(j=2*pos-i),根据对称性 Len[i]=min(mx-i,Len[j]),如果超过之前匹配过的最右字符mx则继续一个个匹配。①当i>=mx时,说明以T[i]为中心的回文串一个都没有进行匹配,所以Len[i]=1,需要一个个继续往下匹配。
尽管做匹配的字符串是原始字符串的两倍,由于每个位置只匹配一次,所以时间复杂度为O(n).
【题目】
【代码】
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
char s[maxn];
char t[maxn<<1];
int Len[maxn<<1];
int len;
void init()
{
int k=0;
t[k++]='@';
for(int i=0;i<len;i++){
t[k++]='#';
t[k++]=s[i];
}
t[k++]='#';
len=k;
}
int Manacher(){
int sum=0,mx=0,pos=0;
for(int i=1;i<len;i++){
if(i<mx) Len[i]=min(mx-i,Len[2*pos-i]);
else Len[i]=1;
while(t[i-Len[i]]==t[i+Len[i]])
Len[i]++;
if(Len[i]+i>mx){
mx=Len[i]+i;
pos=i;
sum=max(sum,Len[i]);
}
}
return sum-1;
}
int main()
{
int n; scanf("%d",&n);
while(n--){
scanf("%s",s);
len=strlen(s);
init();
int ans=Manacher();
printf("%d\n",ans);
}
return 0;
}