hihocode #1032 : 最长回文子串

问题:
时间限制:1000ms
单点时限:1000ms
内存限制:64MB
描述
小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进。

这一天,他们遇到了一连串的字符串,于是小Hi就向小Ho提出了那个经典的问题:“小Ho,你能不能分别在这些字符串中找到它们每一个的最长回文子串呢?”

小Ho奇怪的问道:“什么叫做最长回文子串呢?”

小Hi回答道:“一个字符串中连续的一段就是这个字符串的子串,而回文串指的是12421这种从前往后读和从后往前读一模一样的字符串,所以最长回文子串的意思就是这个字符串中最长的身为回文串的子串啦~”

小Ho道:“原来如此!那么我该怎么得到这些字符串呢?我又应该怎么告诉你我所计算出的最长回文子串呢?

小Hi笑着说道:“这个很容易啦,你只需要写一个程序,先从标准输入读取一个整数N(N<=30),代表我给你的字符串的个数,然后接下来的就是我要给你的那N个字符串(字符串长度<=10^6)啦。而你要告诉我你的答案的话,只要将你计算出的最长回文子串的长度按照我给你的顺序依次输出到标准输出就可以了!你看这就是一个例子。”

样例输入
3
abababa
aaaabaa
acacdas
样例输出
7
5
3
查找最大回文子串,即求出以每个字符为中心的最大回文子串。Manacher算法是经典的算法,该算法的巧妙在于将奇数串与偶数串同时考虑,同时避免了不必要的字符串查找。
描述如下:
为避免奇数与偶数的分类讨论,在字符串中每两个字符之间插入特殊字符#,这样如 abba 变成 #a#b#b#a#, aba变成 #a#b#a#,无论奇数或偶数串都会变成一个新的奇数串,且为了标记字符串头,插入一个特殊字符$防止查找中越界。程序实现如下:

    str_new[0]='$';
    str_new[1]='#';
    n=strlen(str);
    for(i=0;i<n;i++)
    {
        str_new[2*i+2]=str[i];
        str_new[2*i+3]='#';
    }
    int len=2*n+2;
    str_new[len]='\0';

下面是算法的具体查找过程:
用一个辅助数组P 记录以每个字符为中心的最长回文半径,也就是P[i]记录以str_new[i]字符为中心的最长回文串半径。P[i]最小为1,此时回文串为str_new[i]本身。
我们可以对字符串abaab变为新串后写出其P 数组,如下
新串: # a # b # a # a # b #
P[] : 1 2 1 4 1 2 5 2 1 2 1
定义id为i之前最大回文子串所对应的字符下标,mx为当前以id所对应字符为中心的最大回文的最右边;
具体如下图:
这里写图片描述

对于新字符str_new[i],其记录最大回文串P[i]求解如下:

 if(mx>i)
                P[i]=min(P[2*id-i],mx-i);
        else
                P[i]=1;

这样理解,对于新的字符串下标i,当未超过上一记录id最大回文串的最右边,则当前i的回文串(非最大)半径为mx-i或i以id为中心对应点j的回文串中最小一个,(因为mx>i,所以j的最大回文子串半径必定小与id的最大回文半径,id记录的是i之前最大回文子串所在位置;若i有回文串,则j必定有回文串,且在mx范围内,i与j的回文串是相同的;i与j关于id对称)图片解释如下:
这里写图片描述
然后基于当前P[i],继续查找当前i为中心的回文串,直至找到最大回文子串;

 while(str_new[i+P[i]]==str_new[i-P[i]]&&i+P[i]<len&&i-P[i]>0)
        {
               ++P[i];
        }

假设字符串搜索完毕,最大回文子串大小为P[i],接下来证明源字符串最大回文子串大小为P[i]-1;
1、设L为新串中以str_new[i]为中心最长回文串长度,且L=2*P[i]-1;
2、以str_new[i]为中心的回文串一定是以#开头和结尾的,例如“#b#b#”或“#b#a#b#”L减去最后一个#,同时除以2,即为原字符串长度所以L(source)=(L-1)/2,化简后L(source)=P[i]-1。
接下来是程序源代码:

#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#define min(x,y)  x<y?x:y
char str[1000000];
int P[1000000],id,mx,i,n,count;
/*
void init()
{
    str_new[0]='$';
    str_new[1]='#';
    for(i=0;i<strlen(str);i++)
    {
        str_new[2*i+2]=str[i];
        str_new[2*i+3]='#';
    }
    n=strlen(str);
    str_new[2*n+2]='\0';
}
*/
void Manacher()
{   char str_new[1000000];
   str_new[0]='$';
    str_new[1]='#';
    n=strlen(str);
    for(i=0;i<n;i++)
    {
        str_new[2*i+2]=str[i];
        str_new[2*i+3]='#';
    }
    int len=2*n+2;
    str_new[len]='\0';
    count=0;
    mx=0;
    id=0;
    for(i=0;i<len;i++)
    {
        if(mx>i)
                P[i]=min(P[2*id-i],mx-i);
        else
                P[i]=1;

        while(str_new[i+P[i]]==str_new[i-P[i]]&&i+P[i]<len&&i-P[i]>0)
        {
               ++P[i];
        }

        if(mx<P[i])
        {
                mx=P[i];
                id=i;
        }
        if(P[i]>count)
             count=P[i];
    }
    str[0]='\0';
    str_new[0]='\0';
     printf("%d\n",count-1);

}
int main()
{
int m;
scanf("%d",&m);
int k;
for(k=0;k<m;k++)
{
    scanf("%s",str);
    Manacher(); 
}
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值