1 条题解
-
2
题意
给定字符串 ,找出它的最大回文长度。
分析
下面令 。
朴素
枚举每一个子串,判断它是否为回文串即可,时间复杂度 。
这里就不给出代码实现了。哈希
前置知识:字符串哈希 - OI Wiki (oi-wiki.org)
首先预处理出字符串的哈希前缀和哈希后缀,方便快速 比较两个子串是否相等。
然后枚举回文串的对称轴,逐步向左右扩张,比较前后缀是否相等即可。
时间复杂度 。
但是注意到 ,所以这一定是不可过的,考虑优化。
运用你的人类智慧可以发现,回文串的长度是具有单调性的。比如说 ,是一个回文串,那么 也是一个回文串。
这样不就可以直接枚举对称轴然后二分答案长度就可以了吗?
代码:
//the code is from chenjh #include<iostream> #include<cstdio> #include<cstring> #define MAXN 1000005 using namespace std; char s[MAXN]; int n; const int p=103;//选择一个“较大的”且远离 2 的整数次幂的质数可以避免哈希冲突导致错误判断。 unsigned long long p1[MAXN],p2[MAXN],pn[MAXN];//前缀哈希、后缀哈希、p 的 n 次幂。 bool f(int pos,int x,bool op){ return p1[pos]-p1[pos-x]*pn[x]==p2[pos+op]-p2[pos+x+op]*pn[x]; }//判断以 pos 为对称轴,半径为 x 的字符串是否为回文串。 int main(){ pn[0]=1; for(int i=1;i<MAXN;i++)pn[i]=pn[i-1]*p;//预处理 p 的 n 次幂。 for(int cnt=1;cin>>(s+1) && strcmp(s+1,"END");++cnt){ n=strlen(s+1); for(int i=1;i<=n;i++) p1[i]=p1[i-1]*p+s[i];//使用字符串 BKDR 算法,预处理前缀哈希。 p2[n+1]=0; for(int i=n;i;--i) p2[i]=p2[i+1]*p+s[i];//预处理后缀哈希值。 int ans=0; for(int i=1;i<=n;i++){ int l,r; if(i<n && s[i]==s[i+1]){//如果相邻两个字符相等,即回文串长度为偶数。 l=1,r=min(i,n-i);//进行二分长度。 while(l<r){ int mid=(l+r+1)>>1; if(f(i,mid,1)) l=mid; else r=mid-1; } ans=max(ans,l<<1); } l=1,r=min(i,n-i+1);//以第 i 个字符为对称轴,即回文串长度为奇数。 while(l<r){ int mid=(l+r+1)>>1; if(f(i,mid,0)) l=mid; else r=mid-1; } ans=max(ans,(l<<1)-1); } printf("Case %d: %d\n",cnt,ans); } return 0; }
时间复杂度 ,十分优秀了!
但是……这个怎么可能足够呢?!
Manacher
前置知识:Manacher - OI Wiki (oi-wiki.org)
该算法由 Glenn K. Manacher 在 1975 年提出。
其实这个问题可以使用后缀数组和快速 LCA 该问题可在 时间内解决。
但是这里描述的算法 压倒性 的简单,并且在时间和空间复杂度上具有更小的常数。
鉴于 OI Wiki 介绍的十分详细,这里就不过多进行阐述。
为了方便处理,我加入了 来表示空字符。
在字符串开头我加入了 来防止数组越界。
剩下的就十分简单了。
代码:
//the code is from chenjh #include<cstdio> #include<cstring> #include<algorithm> #define MAXN 1000005 char s[MAXN],t[MAXN<<1]; int p[MAXN<<1];//定义 p[i] 为为字符 t[i] 为中心的最长回文子串的比南京。 void manacher(int len){ int l=0; t[l++]='$',t[l++]='#';//加入特殊字符 '$' 和 '#'。 for(int i=0;i<len;i++) t[l++]=s[i],t[l++]='#'; t[l]=0; memset(p,0,sizeof p);//多测不清空,爆零两行泪! for(int i=0,r=0,c=0;i<l;i++){ int &P=p[i]; P=r>i?std::min(p[(c<<1)-i],r-i):1; for(;t[i+P]==t[i-P];++P); if(i+P>r) r=i+P,c=i; } } int main(){ for(int c=1;~scanf("%s",s)&&strcmp(s,"END");++c){ int len=strlen(s),ans=0;manacher(len); for(int i=0,mi=(len+1)<<1;i<mi;i++) ans=std::max(ans,p[i]-1);//找到最大回文长度。 printf("Case %d: %d\n",c,ans); } return 0; }
- 1
信息
- ID
- 2959
- 时间
- 15000ms
- 内存
- 64MiB
- 难度
- 10
- 标签
- (无)
- 递交数
- 6
- 已通过
- 2
- 上传者