HDU3068 最长回文
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 16036 Accepted Submission(s): 5874
Problem Description
给出一个只由小写英文字符a,b,c...y,z组成的字符串S,求S中最长回文串的长度.
回文就是正反读都是一样的字符串,如aba, abba等
回文就是正反读都是一样的字符串,如aba, abba等
Input
输入有多组case,不超过120组,每组输入为一行小写英文字符a,b,c...y,z组成的字符串S
两组case之间由空行隔开(该空行不用处理)
字符串长度len <= 110000
两组case之间由空行隔开(该空行不用处理)
字符串长度len <= 110000
Output
每一行一个整数x,对应一组case,表示该组case的字符串中所包含的最长回文长度.
Sample Input
aaaa abab
Sample Output
4 3
Source
Analisys
这道题可以用一种叫做manacher 马拉车 的算法来做。
manacher算法
一种求字符串中最长回文串的O(N)的算法。
首先回文串有奇偶两种,即长度为奇数、偶数的回文串,为了方便地解决这个问题,我们就在原创中每两个字符中间加一个0(也可以是别的字符,只要一样就行,头尾不加),这样就都变成了奇串。但是实际操作中,我们并不用真的在数组里加0,只需要利用下标实现。
我们先假想我们已经在原串中加好了0,所有的字符下标都变成了原来的两倍,那么易知下标为偶数的字符对应原串中的字符,下标为奇数的字符为0
我们令b[i]表示串中以第i个字符为对称中心的串的一半长度(奇串包含中心),注意这个长度是在原串中的长度,现在从头到尾开始枚举i,令rightest pointer(以下简记为rp)表示前面所有回文串中能达到的最右的位置(这个是在真实串中的位置),j表示这个串的对称中心。那么以i为中心的一个回文串由j位置对应过去一定也是一个回文串,因为你已经知道了那个对称串的b值,所以当前b值至少是b[2j-i](由等差中项的性质可知x+i=2j,所以x=2j-i),这个是可以证明的,画画图就很好理解了,然后你可以再暴力扩展当前串,扩展一旦发生,就可以令j=i,同时rp++
这个算法用纯文字真的不好讲啊。。。(吐槽别在意)
这个算法唯一混乱的地方就在于下标,注意只有i、j是指向加过0的串中的位置,其他变量都是针对原串的,因为我们加入0的初衷就是为了能处理奇偶串,并没有实际意义,所以只有i、j这两个循环变量是不一样的,其它的都记录的是正常值。还有你枚举的i是对称中心i,设两个变量lmid,rmid,lmid=i/2,rmid=(i+1)/2,分别表示对称中心(原串)的左右两个中心,当这个串是奇串时lmid==rmid,否则差1,在你扩展串时计算左右边界会很方便,( [lmid-b[i]+1,rmid+b[i]-1]就是这个回文串的范围 )。
算法伪代码:
b[0]=1;
for(i=j=rp=0;i<=right;i++)
begin
lmid=i div 2, rmid=(i+1) div 2;
b[i]=min( rp-i+1 , b[2*j-1] );
for(;(lmid-b[i]>0) and (rmid+b[i]<=right) and (a[lmid-b[i]==a[lmid+b[i]]);j=i,rp++,b[i]++ );
end
PS:这个算法和“马拉车”真的没什么关系,只是谐音而已
Code
//hdu3068 最长回文 manacher
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxlen 300000
using namespace std;
char s[maxlen];
int right, b[maxlen];
bool init()
{
if(scanf("%s",s)==EOF)return false;
right=strlen(s)-1;
return true;
}
int manacher()
{
int i, j, rp, lmid, rmid, ans;
b[0]=1;
j=0;
rp=0;
ans=1;
for(i=1;i<=(right<<1);i++)
{
lmid=i>>1;rmid=(i+1)>>1;
b[i]=min(rp-rmid+1,b[(j<<1)-i]);
for(;lmid-b[i]>=0 && rmid+b[i]<=right
&& s[lmid-b[i]]==s[rmid+b[i]];
b[i]++,rp++,j=i);
ans=max(ans,(b[i]<<1)-!(j&1));
}
printf("%d\n",ans);
}
int main()
{
while(init())manacher();
return 0;
}