poj3974 最长回文子串
题目描述
输入格式
多组数据,每次输入一个字符串,以"END"结尾
输出格式
对于每个字符串,输出它最长回文子串的长度
AC代码
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<string>
#include<cstring>
using namespace std;
int P=131,len;
char s[1000006];
unsigned long long p[1000006],hf[1000006],hb[1000006];
unsigned long long get1(int x,int y){/*
s 1 2 3 4 5 6 7 8 9 10
hf[r] 1 2 3 4 5
hf[l-1] 1 2 3
hf[l-1]*p[r-l+1] 1 2 3 0 0
4~5 0 0 0 4 5
*/
return (hf[y]-hf[x-1]*p[y-x+1]);
}
unsigned long long get2(int x,int y){
return (hb[x]-hb[y+1]*p[y-x+1]);//注意:hb数组是相反的!
}
int main()
{
int id=0;
p[0]=1;
for(int i=1;i<=1000005;i++){
p[i]=P*p[i-1];
}
while(scanf("%s",s+1)&&!(s[1]=='E'&&s[2]=='N'&&s[3]=='D')){
id++;
len=strlen(s+1);
int ans=0;
for(int i=1;i<=len;i++){
hf[i]=hf[i-1]*P+s[i];
}
for(int i=len;i>=1;i--){
hb[i]=hb[i+1]*P+s[i];
}
for(int i=1;i<=len;i++){
int l=0,r=min(i-1,len-i);
while(l<r){
int mid=(l+r+1)>>1;
if(get1(i-mid,i-1)==get2(i+1,i+mid))
l=mid;
else
r=mid-1;
}
ans=max(ans,l*2+1);
l=0,r=min(i-1,len-i+1);
while(l<r){
int mid=(l+r+1)>>1;
if(get1(i-mid,i-1)==get2(i,i+mid-1))
l=mid;
else
r=mid-1;
}
ans=max(ans,l*2);
}
printf("Case %d: %d\n",id,ans);
}
return 0;
}
思路整理
这题暴力思路很明显,我们只要枚举每个字符做回文中心,逐位向两侧扩展判断字符是否相同就OK。然而数据很大,O(n^2)的复杂度100%会TLE。
不过从暴力思路中,我们可以发现正解的影子。同样是向两侧扩展,我们没有必要逐位枚举。
这时我们可以先预处理字符串的两个哈希值(一个向前,一个向后),然后枚举每个字符做回文中心(注意奇回文和偶回文)。这时,我们可以二分查找回文串的长度,范围从0到回文中心与字符串两侧的距离,再判断回文串相同的部分的哈希值是否相等(这时两个哈希值就有用了)。
于是经过了一番瞎搞,时间复杂度成功被我们降到O(nlogn)。这样一来解题就没有困难了
然而在实际操作时,l到r区间Hash值的求法对我来说很难理解(本人菜请见谅),于是我做了一个这个:
s 1 2 3 4 5 6 7 8 9 10
hf[r] 1 2 3 4 5
hf[l-1] 1 2 3
hf[l-1]*p[r-l+1] 1 2 3 0 0
4~5 0 0 0 4 5
在这个简陋的表格中,每进一位(数位)相当于哈希值P,于是r的哈希值减去经过补位(也就是(r-l+1)个p)后的l的哈希值,就可以得出l到r区间的哈希值。