前言:kmp是预处理最大前后缀的思路来跳转,马拉车利用了回文子串的半径来达到减少冗余。
如何求解一串字符串中的最长回文子串。
1.暴力O(n^3)
枚举字符串再check
2.暴力O(n^2)
枚举对称中心,然后两个指针由此为中心拓展,更新答案,O(n^2)
3.二分+哈希O(nlogn)
由于一个回文串,知道了对称中心后,左边的串和右边的相同。因此其实存在单调性。
S【i...j】为回文串,则S【i+1,j-1】为回文串。因此可以二分半径长度。而字符串的判定可以哈希来做。
但是有时候实际用起来比较容易调边界,并且单哈希可以卡,log常数如果拉到1e7级别的基本是过不来了的。
4.马拉车O(n)
回文串的特征是中心对称。开一个p[i]数组来预处理以i为对称中心的最大半径。同时以pos记录中点,maxr记录当前的最长回文子串的最右端点。
如果一个回文串对称中心的一侧有另一个回文子串,那么对称过去也有一个相同长度的回文子串。
manacher的思路就是以此优化,减少暴力的寻找。
( 图源https://www.luogu.com.cn/user/53930 )
黄色部分完全一致,这就是manacher的核心。
如上图的情况,i<maxr。黄色部分完全一致。此时可以直接更新p[i]=p[j]。
或者如下面的情况,j的p[j]长度过大导致越界。因此i此时更新可以保证的范围是maxr-i这一段长度。
所以每一次若i在<maxr的范围内,我们要在这两者的中取min。
同时要注意,当p[i]更新完后,i点仍然可能继续更新产生更大半径。因此要继续更新该点的半径。但是此时已经更新了一部分p[i]。直接从i-p[i]-1与i+p[i]+1的位置开始比较这两端的字母。大大地优化了一段距离的重复查找。
而如下者情况,我们无法保证i的最大半径。所以按照上述暴力更新。
当前更新完了之后。判断i+p[i]是否超过maxr。超过后就可以更新maxr,同时将pos移到i位置。如此往复下去。
模板题:https://www.luogu.com.cn/problem/P3805
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<set>
#include<cstdio>
#include<algorithm>
#define debug(a) cout<<#a<<"="<<a<<endl;
using namespace std;
const int maxn=2e7+1000;
typedef long long LL;
inline LL read(){LL x=0,f=1;char ch=getchar(); while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;}
char s[maxn*2],ss[maxn*2];
int p[maxn*2],maxr,pos;
void manacher(char* str,int len)
{
for(int i=1;i<=len;i++)
{
if(i<maxr) p[i]=min(p[(pos<<1)-i],maxr-i);
while( str[i-p[i]-1 ] == str[i+p[i]+1] ) ++p[i];
if(i+p[i]>maxr) maxr=i+p[i],pos=i;
}
}
int main(void){
scanf("%s",s+1);int n=strlen(s+1);
ss[0]='@';ss[1]='#';
for(int i=1;i<=n;i++) ss[i<<1]=s[i],ss[i<<1|1]='#';
strcpy(s,ss);
manacher(s,strlen(s));
int ans=1;
n=(n<<1)|1;
for(int i=1;i<=n;i++)
{
ans=max(ans,p[i]);
}
cout<<ans<<"\n";
return 0;
}