什么是manachar?
Manachar算法大概就是可以O(n)求出一个串的最长回文子串。名字听起来很高端,但实际上想法和实现都很简单。
主要思想就是充分了利用回文串的对称性,使复杂度降至O(n)。
算法实现
具体如何搞呢?
1. 先对原创进行一次预处理,在头和尾以及相邻字符中间都插入一个无关字符,例如:
原串:adcdad 新串:#a#d#c#d#a#d#
这样搞就不必分类讨论回文串长度的奇偶性了,所有的回文串长度都是奇的,为算法提供了很多便利。
以下我们就可以不管原串了,直接在新串上搞事情。
2. 我们设
RL[i]
表示以i为对称中心,向两边能延伸的长度(包括i点)。
即RL[i]=Len_{以i为对称中心的最长回文串} div 2+1。
3. 看下面的栗子:
s : # a # d # c # d # a # d #
RL: 1 2 1 2 1 4 1 2 1 4 1 2 1
显然我们只需求出RL数组,然后
max(RL[i]−1)
即是最后答案。
我们从左往右依次求解,假设现在RL[1~i-1]都已得到,现在要求RL[i]:
记maxR表示之前求出的所有回文串的右端点的最大值,pos表示对应的对称中心。
如果
i<maxR
:
设j为i关于pos的对称点。如图
由于1~pos与pos~maxL是对称的,利用RL[j]的信息我们可以初步得到RL[i]的可能值,注意如果超过maxR要扔掉。然后这不一定就是极限了,因为maxR之后的情况有所不同,还要再一步一步推一下。
如果
i>=maxR
:maxR之后都是未知区域,就需要从长度为1开始推了。
算出RL[i]后更新pos和maxR。
这样就结束了。
复杂度为什么是线性呢?显然maxR是不断变大的,且每次手动推一步就意味着maxR即将变大,且推的步数就对应maxR增加的大小。所以总复杂度O(n)。
下面是代码实现:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=250005;
char s[maxn],st[maxn];
int RL[maxn];
int Manacher(){
int len=strlen(s+1), n=1;
st[1]='#'; for(int i=1;i<=len;i++) st[++n]=s[i], st[++n]='#';
int pos=0,maxR=0,res=0;
for(int i=1;i<=n;i++){
RL[i]=i<maxR?min(RL[2*pos-i],maxR-i+1):1;
while(i-RL[i]>0&&i+RL[i]<=n&&st[i-RL[i]]==st[i+RL[i]]) RL[i]++;
if(i+RL[i]-1>maxR) maxR=i+RL[i]-1, pos=i;
res=max(res,RL[i]-1);
}
return res;
}
int main(){
freopen("hdu3068.in","r",stdin);
freopen("hdu3068.out","w",stdout);
for(scanf("%s",s+1);s[1];s[1]=0,scanf("%s",s+1)) printf("%d\n",Manacher());
return 0;
}