题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=3068
题目描述:
Problem Description
给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度.
回文就是正反读都是一样的字符串,如aba, abba等
Input
输入有多组case,不超过120组,每组输入为一行小写英文字符a,b,c...y,z组成的字符串S
两组case之间由空行隔开(该空行不用处理)
字符串长度len <= 110000
Output
每一行一个整数x,对应一组case,表示该组case的字符串中所包含的最长回文长度.
Sample Input
aaaa
abab
Sample Output
4
3
解题思路:
计算字符串的最长回文字串最简单的算法就是枚举该字符串的每一个子串,并且判断这个子串是否为回文串,这个算法的时间复杂度为O(n^3)的,显然无法令人满意,稍微优化的一个算法是枚举回文串的中点,这里要分为两种情况,一种是回文串长度是奇数的情况,另一种是回文串长度是偶数的情况,枚举中点再判断是否是回文串,这样能把算法的时间复杂度降为O(n^2),但是当n比较大的时候仍然无法令人满意,Manacher算法可以在线性时间复杂度内求出一个字符串的最长回文字串,达到了理论上的下界。
我们先看最长回文串的几个关键点问题:
一、显然,当我们找到中心点时,利用回文串的特性进行双向遍历是最好的解决办法,但是由于奇偶性的问题导致我们无法准确的定位中心点
二、遍历所有回文串显然有些不必要的废操作,因为必然存在许多子回文串在较大的回文串内部,比如说abcdcba,他就包含了bcdcba……所以我们考虑的就是如何把这些过滤掉。
马拉车Manacher算法就很巧妙的解决了这两个问题:
将长度为奇数的回文串和长度为偶数的回文串一起考虑,具体做法是,在原字符串的每个相邻两个字符中间插入一个分隔符,同时在首尾也要添加一个分隔符,分隔符的要求是不在原串中出现,比如说abcdcba 就变成了#a#b#c#d#c#b#a#,#也可以换成其他的,只要原串中不涉及到的字符即可。
如果变化后,回文串一定会是一个奇数,因为#的长度是原串长度+1 即新串总长度为2*原串长度+1
解决完奇偶性问题之后,接着用一个辅助数组Len[i]表示以字符T[i]为中心到边缘的长度,也就是以T[i]为中心点的回文串总长度/2+1
举两个例子:
T=#a#b#c#d#c#b#a#,那么Len即121212171212121
T=#a#a#a#b#a# ,那么Len即12343214121
所以我们要做的事,就是求最大的Len[i]即可
有了Len数组,我们就很容易判断出当前遍历的情况,设p_r为之前计算中回文串的最右端点,并且设取得这个最大值的中心点位置为po
对于当前点i,如果pr点大于i点,那么说明i点在po为中心这个最大回文串内部,我们设与其对称的一个点j,即从i点到po 和从po点到i是对称的。
那么我们可以认为:
以点j作为中心点的回文串(len[j])并且满足其在po回文串内部的长度(mx-i)对于i点来说 一样是对称的。
如果i点大于p_r,即之前并没有遍历过,那么肯定要从头开始匹配了。所以我们现在基本可以知道:对于字符串中的每一个位置,只进行一次匹配,所以Manacher算法的总体时间复杂度为O(n),其中n为字符串的长度,当前,其长度是原始字符串长度的2倍。另外,为了避免匹配时数组越界,我们在字符串两边加入两个不同的特殊字符,即处理后的字符串tmp是下标从1开始的,原始字符串第一个字符应该在tmp[2]
hdu3068AC代码:
#include<stdio.h>#include<string.h>#include<math.h> #define min(a,b) a>b?b:a;#define max(a,b) a>b?a:b;const int maxn=110010;char str[maxn];//原字符串char tmp[maxn<<1];//转换后的字符串int Len[maxn<<1];//转换原始串int INIT(char *st){int i,len=strlen(st);tmp[0]='@';//字符串开头增加一个特殊字符,防止越界for(i=1;i<=2*len;i+=2){tmp[i]='#';tmp[i+1]=st[i/2];}tmp[2*len+1]='#';tmp[2*len+2]='$';//字符串结尾加一个字符,防止越界tmp[2*len+3]=0;return 2*len+1;//返回转换字符串的长度}//Manacher算法计算过程int MANACHER(char *st,int len){int p_r=0,ans=0,po=0;//p_r是未涉及到的最左点 即 mx-1为po中心点作为回文串的右坐标 for(int i=1;i<=len;i++){if(p_r>i){Len[i]=min(p_r-i,Len[po-(i-po)]);//Len[j]点中满足在po回文串内部的长度 }else {Len[i]=1;//如果i>=p_r,要从头开始匹配}while(st[i-Len[i]]==st[i+Len[i]])Len[i]++;if(Len[i]+i>p_r)//刷新po、p_r记录 {p_r=Len[i]+i;po=i;}ans=max(ans,Len[i]);}return ans-1;//返回Len[i]中的最大值-1即为原串的最长回文子串额长度 }int main(){ while(gets(str)) { INIT(str); //puts(tmp); printf("%d\n",MANACHER(tmp,strlen(tmp))); memset(str,'\0',sizeof(str));getchar();} return 0;}
部分内容借鉴于http://blog.csdn.net/dyx404514/article/details/42061017
共同学习,写下你的评论
评论加载中...
作者其他优质文章