一.最长回文子串问题与Manacher算法.
回文串,是一种满足一定条件的字符串,设字符串S的第i位为 (字符串从1开始),那么S为回文串仅当 .
一个串的最长回文子串定义为,这个串的一个最长的子串满足这个子串为回文串.
求最长回文子串的朴素算法是
的,但是Manacher算法可以在线性时间复杂度内求解一个串的最长回文子串.
二.Manacher算法流程.
我们在看Manacher算法前,先来看朴素算法.朴素算法的思路就是枚举对称位置,然后从对称位置开始向两边拓展直到失配,时间复杂度为 .
但是我们发现这个朴素算法的对称中心有可能是一个字符,也有可能是两个字符中间的空隙,这样自己并不好处理.所以我们在所有字符的空隙间插入一个在原串中不会出现的字符,并在开头加上两个,末尾加上一个避免边界判定.
比如下图中第一个串处理后就变成了第二个串:
处理完之后,我们发现现在对称中心就只会是在字符上了.并且考虑一个在处理过的串上的一个回文子串
,设它的对称中心为字符
,那么在原串上对应的回文子串长度就是
的长度减1.那么现在的问题就变成了求对于每个mid值,求以它为中心拓展的最长的
长度,我们设这个值为
.
接下来的部分与扩展KMP比较像,如果有兴趣的可以去看看扩展KMP.
我们设绿色指标前的所有pal我们都已经处理出来了,红色线段是一个回文串,而且以红色指标为对称中心,且蓝色与绿色指标关于红色指标对称.通过回文串的定义,我们发现一个回文串翻转后仍然是回文串,所以蓝色指标的pal值小于或等于绿色指标的pal值.
到这里我们就可以设计出一个算法了.与扩展KMP类似的,我们记录一个当前右端点最右边的红色线段的对称中心p.然后在求解i的时候,我们找到它的对应点
,分成两种情况:
1.
,也就是i在右端点最右的红色线段之外,这时我们发现没有任何信息可以使用,所以直接暴力拓展,并更新p.
2.
,这个时候我们有信息可以用,也就是先让
.如果还可以往右拓展就暴力拓展,并更新p.
注意到这个算法只有当一个点没有被拓展过,才会被拓展一次,所以时间复杂度是 的.
代码如下:
int manacher(char *c,int len){
int n,ans=0;
tmp[1]='#';tmp[n=2]='#';
for (int i=1;i<=len;++i)
tmp[++n]=c[i],tmp[++n]='#';
int p=0,t;
for (int i=1;i<=n;++i){
pal[i]=i<=p+pal[i]-1?pal[2*p-i+1]:1;
while (tmp[i+pal[i]]==tmp[i-pal[i]]) ++pal[i];
if (i+pal[i]>p+pal[p]) p=i;
ans=max(ans,pal[i]-1);
}
return ans;
}
三.例题与代码.
题目:luogu3805.
代码如下(被卡常只有75分):
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=11000000;
char tmp[N*2+9];
int pal[N*2+9];
int manacher(char *c,int len){
int n,ans=0;
tmp[1]='#';tmp[n=2]='#';
for (int i=1;i<=len;++i)
tmp[++n]=c[i],tmp[++n]='#';
int p=0,t;
for (int i=1;i<=n;++i){
pal[i]=i<=p+pal[i]-1?pal[2*p-i+1]:1;
while (tmp[i+pal[i]]==tmp[i-pal[i]]) ++pal[i];
if (i+pal[i]>p+pal[p]) p=i;
ans=max(ans,pal[i]-1);
}
return ans;
}
char c[N+9];
int n;
Abigail into(){
scanf("%s",c+1);
n=strlen(c+1);
}
Abigail outo(){
printf("%d\n",manacher(c,n));
}
int main(){
into();
outo();
return 0;
}