如果一个字符串正着读和倒着读是一样的,则称它是回文的。
给定一个长度为 N 的字符串 S,求他的最长回文子串的长度是多少。
输入格式
输入将包含最多 30个测试用例,每个测试用例占一行,以最多 1000000 个小写字符的形式给出。
输入以一个以字符串 END
开头的行表示输入终止。
输出格式
对于输入中的每个测试用例,输出测试用例编号和最大回文子串的长度(参考样例格式)。
每个输出占一行。
输入样例:
abcbabcbabcba
abacacbaaaab
END
输出样例:
Case 1: 13
Case 2: 6
解法:哈希+manachar扩展
跟上题兔子兔子差不多,O(n)预处理前缀哈希值,每次O(1)计算任意区间哈希值;
求回文字符串,意味着左边区间的前缀哈希值=右边区间的后缀哈希值;
书上解法是二分寻找半径长度,判断是否回文,时间复杂度O(n*logn);
我们这里做一个manacher的优化,每两个字符间填一个'#'(包括开头结尾),这样不会影响前缀哈希值和后缀哈希值的计算结果;
不知道 manacher的看这里 manacher详解
优化后就不用考虑回文长度是偶数还是奇数了,因为添加字符后回文子串的中心一定有一个字符(字母或者'#');
这样就不会出现添加前偶回文字符串中心什么都没有的情况;
然后经过manechar优化后的字符串,它的真实的回文长度=回文半径-1,回文长度-2*回文半径=1;
例如abcbc,添加前长度=5,回文串为bcbc,长度=4;
添加后为#a#b#c#b#c#,长度=11,回文串=#b#c#b#c#,长度=9,半径=5,
真实回文长度=半径-1 = 4(这也是用了manachar的思想);
manacher优化后我们直接判断当前中心+已经判断的长度是不是一个回文串,如果是回文串,我们再试着增加它的回文长度;
如果不是的话我们直接跳过,这样时间复杂度几乎为O(n);
#include<iostream>
#include<string.h>
using namespace std;
const int N=1e7+10,base=131;//base表示进制
using ULL=unsigned long long;
char s[N],str[N];//s表示填充前的字符串,str表示填充后的字符串;
ULL h1[N],h2[N],p[N];//h1表示哈希前缀和,h2表示后缀和,p表示进制次方;
int main()
{
int T=0;
while(scanf("%s",s+1),strcmp(s+1,"END")){
int n=strlen(s+1);
int len=0;//len表示填充后的长度
for(int i=1;i<=n;i++){
str[++len]='#';//填充'#'
str[++len]=s[i];
}
str[++len]='#';//末尾填充
str[len+1]='\0';
p[0]=1;
for(int i=1,j=len;i<=len,j>=1;++i,--j){//求前缀和后缀,进制次方
h1[i]=h1[i-1]*base+str[i]-'a'+1;
p[i]=p[i-1]*base;
h2[j]=h2[j+1]*base+str[j]-'a'+1;
}
int res=0,r=0;//res表示最终结果,r表示填充后的回文串的半径-1,也就是真实长度
for(int i=1;i<=len;++i){
r=res;
if(i+r>=len) break;//判断边界
if(h1[i+r]-h1[i-1]*p[r+1]!=h2[i-r]-h2[i+1]*p[r+1]) continue;//最大的半径组成的是不是回文串
while(str[i+r+1]==str[i-r-1]&&i+r+1<=len&&i-r-1>0) r++;//如果是判断能否扩展
res=max(res,r);
}
cout<<"Case "<<++T<<": "<<res<<endl;
}
return 0;
}