概述

对于求最长回文子串的暴力算法,一般是枚举对称轴然后不断扩展,时间复杂度$O(n^2)$。
暴力算法有两点缺点:

  1. 对称轴可能是一个字母也可能是两个字母之间,需要两次枚举。
  2. 扩展可能会出现多余的重复。

而Manacher算法在暴力算法的基础上,做了一些优化,使得复杂度缩小为了$O(n)$。

Step 1

对于缺点1,可以在字符串中加上一些不会出现的字符作分隔符,这样就可以把“以两个字符的分界线作为对称轴”变为“以两个字符之前的字符作为对称轴”。

比如下面这个字符串:

abcbcbba

可以变为

a#b#c#b#c#b#b#a

可以在最前面和最后面加上另外两个同样不会出现的不同的字符,方便判断边界。
(后面全部用新串)

Step 2

设$r[x]$表示以x为中心回文子串的能扩展的最大距离
例如

a#b#c#b#c#b#b#a

以第7个字符(b)为中心能扩展的最大距离是5(第2个字符到第12个字符),所以$r[7]=5$

Manacher算法需要用到mx和md两个值,其中mx表示访问的回文子串所能达到的最右端的位置(也就是$x+r[x]$的最大值),而md表示扩展到最右端对应的x。
比如

a#b#c#b#c(#)b#b#a

在计算到带括号的#这一位时,mx=12,md=7

算法流程:
计算r[x]:
当$x\le mx$时,r[x]初始化为min(r[x关于md的对称点],mx-x),否则r[x]初始化为0;

意思是:如果x在这个回文子串中,那么r[x]至少也是和r[x的对称点]和x离最右边界的距离的最小值。

如果r[x的对称点]<=mx-x,那么说明以r的对称点为中心的最长回文子串包含在了以md为中心的最长回文子串中,那么以r为中心的最长回文子串也是这么长。

如果r[x的对称点]>mx-x,那么我们只能确定mx-x这一部分。

接着暴力扩展就好了。暴力扩展就好了。暴力扩展就好了。

然后更新一下mx和md。

复杂度$O(n)$,特别神奇。

代码:

/*
Author: CNYALI_LK
LANG: C++
PROG: 1088.cpp
*/
#include<bits/stdc++.h>
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define all(x) x.begin(),x.end()
using namespace std;
const double eps=1e-8;
const double PI=acos(-1.0);
typedef long long ll;
template<class T>int chkmin(T &a,T b){return a>b?a=b,1:0;}
template<class T>int chkmax(T &a,T b){return a<b?a=b,1:0;}
template<class T>T sqr(T a){return a*a;}
template<class T>T mmin(T a,T b){return a<b?a:b;}
template<class T>T mmax(T a,T b){return a>b?a:b;}
template<class T>T aabs(T a){return a<0?-a:a;}
#define min mmin
#define max mmax
#define abs aabs
char s[102424];
int a[204848],p[204848];
int js(int x,int l){
    if(x&1)
        if(l&1)
            return (l+1);
        else 
            return l;
    else return l|1;
}
void manacher(int n){
    int mx=0,m=0,ans=0;
    for(int i=1;i<=n;++i){
        if(i>=mx)p[i]=1;
        else p[i]=min(p[m*2-i],mx-i+1);
        while(a[i-p[i]]==a[i+p[i]])++p[i];
        --p[i];
        if(chkmax(mx,i+p[i]))m=i;
//      printf("%d %d\n",p[i]);
        chkmax(ans,js(i,p[i]));
    }   
    printf("%d\n",ans);
}
int main(){
#ifdef cnyali_lk
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
#endif
    scanf("%s",s+1);    
    a[0]=-1;
    int len=strlen(s+1);
    for(int i=1;i<=len;++i){
        a[i<<1]=s[i];
    }
    int n=2*len+1;
    a[n+1]=-2;
    manacher(n);
    return 0;
}

标签: none

添加新评论