最长回文(HDU-3068)


1 题意

  求一个字符串的最长回文子串。
  链接:link


2 思路

2.1 Manacher

  下面先介绍 M a n a c h e r Manacher Manacher算法。
  假设 s t r str str为待处理字符串, l e n [ i ] len[ i ] len[i]数组存放以该 s t r [ i ] str[ i ] str[i]字符为中心的最长回文半径, m i d mid mid为当前最长回文子串的中点, m x mx mx为当前最长回文子串的右边界.。
  在开始算法之前,先在字符串中首部填充$,字符之间#。比如,字符串abaaba填充之后就变成了$#a#b#a#a#b#a#($与#未在字符串中出现)。
  那么对当前位置 i i i有如下伪代码:(此时已经知道前 i − 1 i - 1 i1个字符的最长回文半径)

  1. i < m x i < mx i<mx,则 l e n [ i ] = m i n ( l e n [ 2 × m i d − i ] , m x − i ) len[ i ] = min ( len[ 2 \times mid - i ] , mx - i ) len[i]=min(len[2×midi],mxi)
  2. 否则 l e n [ i ] = 1 len[ i ] = 1 len[i]=1
  3. i i i位置上的字符向两边进行匹配,更新 l e n [ i ] len[ i ] len[i]的值,更新结束后更新 m i d mid mid m x mx mx的值。

  命题:如果 i < m x i < mx i<mx,那么 i i i为中心的最长回文半径最少为 m i n ( l e n [ 2 × m i d − i ] , m x − i ) min ( len[ 2 \times mid - i ] , mx - i ) min(len[2×midi],mxi)
  证明
  首先 i < m x i < mx i<mx说明 i i i坐落于以 m i d mid mid为中心的回文子串中。在该回文子串范围内,左右两边是完全对称的,所以 i i i的最长回文半径可参考其对称点 2 × m i d − i 2 \times mid - i 2×midi的最长回文半径。
  已知其对称点 2 × m i d − i 2 \times mid - i 2×midi最长回文半径为 m i n ( l e n [ 2 × m i d − i ] , m x − i ) min ( len[ 2 \times mid - i ] , mx - i ) min(len[2×midi],mxi),所以 i i i的最长回文半径至少为 m i n ( l e n [ 2 × m i d − i ] , m x − i ) min ( len[ 2 \times mid - i ] , mx - i ) min(len[2×midi],mxi),超出 m x mx mx的部分暴力匹配即可。

  $#a#b#a#a#b#a#中以#为中心的最长回文子串去掉#后对应原字符串的最长偶回文串,以原字符为中心的最长回文子串去掉#后对应原字符串的最长奇回文串。而去掉#后的最长回文串的长度正好等于回文半径减 1 1 1,这样问题转化为了求$#a#b#a#a#b#a#的最长回文半径。

2.1.1 时间复杂度分析

  由于每个字符只会被匹配一次,所以时间复杂度为 O ( n ) \mathcal{O}(n) O(n)

2.1.2 实现

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=110010;
char s[MAXN],Ma[MAXN*2];
int Mp[MAXN*2];
int Manacher(char *s,int len){
    int l=0;Ma[l++]='$',Ma[l++]='#';
    for(int i=0;i<len;i++) Ma[l++]=s[i],Ma[l++]='#';
    Ma[l]=0;
    int res=0;
    for(int mx=0,id=0,i=0;i<l;i++){
        Mp[i]=mx>i?min(Mp[2*id-i],mx-i):1;
        while(Ma[i+Mp[i]]==Ma[i-Mp[i]]) Mp[i]++;
        if(i+Mp[i]>mx) mx=i+Mp[i],id=i;
        res=max(res,Mp[i]-1);
    }
    return res;
}
int main(){
    while(~scanf("%s",s)){
        printf("%d\n",Manacher(s,strlen(s)));
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值