转载请注明出处,谢谢http://blog.youkuaiyun.com/acm_cxlove/article/details/7854526 by---cxlove
最长回文是一个经典的例子。
首先是URAL 1297。范围只有1000,可以用后缀数组解决
在原串后面加一个特殊符号之后,将原串反向之后加在后面,然后比较height的最大值。
其中需要判断两个sa,一个在前面一个在后面才可以。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<string>
#include<vector>
#include<algorithm>
#include<map>
#include<set>
#define maxn 250005
#define eps 1e-8
#define zero(a) fabs(a)<eps
using namespace std;
//以下为倍增算法求后缀数组
int wa[maxn],wb[maxn],wv[maxn],Ws[maxn];
int cmp(int *r,int a,int b,int l)
{return r[a]==r[b]&&r[a+l]==r[b+l];}
void da(const char *r,int *sa,int n,int m){
int i,j,p,*x=wa,*y=wb,*t;
for(i=0;i<m;i++) Ws[i]=0;
for(i=0;i<n;i++) Ws[x[i]=r[i]]++;
for(i=1;i<m;i++) Ws[i]+=Ws[i-1];
for(i=n-1;i>=0;i--) sa[--Ws[x[i]]]=i;
for(j=1,p=1;p<n;j*=2,m=p){
for(p=0,i=n-j;i<n;i++) y[p++]=i;
for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0;i<n;i++) wv[i]=x[y[i]];
for(i=0;i<m;i++) Ws[i]=0;
for(i=0;i<n;i++) Ws[wv[i]]++;
for(i=1;i<m;i++) Ws[i]+=Ws[i-1];
for(i=n-1;i>=0;i--) sa[--Ws[wv[i]]]=y[i];
for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
return;
}
int sa[maxn],Rank[maxn],height[maxn];
//求height数组
void calheight(const char *r,int *sa,int n){
int i,j,k=0;
for(i=1;i<=n;i++) Rank[sa[i]]=i;
for(i=0;i<n;height[Rank[i++]]=k)
for(k?k--:0,j=sa[Rank[i]-1];r[i+k]==r[j+k];k++);
return;
}
char str[maxn];
int main(){
while(scanf("%s",str)!=EOF){
int l=strlen(str);
str[l]=1;
for(int i=l+1,j=l-1;j>=0;i++,j--)
str[i]=str[j];
int n=2*l+1;
str[n]='\0';
da(str,sa,n+1,250);
calheight(str,sa,n);
int idx=0,ans=1;
for(int i=1;i<=n;i++){
int L=min(sa[i],sa[i-1]),R=max(sa[i],sa[i-1]);
if(L>l||R<l) continue;
if(L+height[i]!=n-R) continue;
if(height[i]>ans){
ans=height[i];
idx=L;
}
else if(height[i]==ans&&L<idx)
idx=L;
}
for(int i=idx,j=0;j<ans;j++,i++) printf("%c",str[i]);
printf("\n",ans);
}
return 0;
}
但是这个算法并不是最高效的,对于HDU 3068,11W的字符长度,就会TLE
http://acm.hdu.edu.cn/showproblem.php?pid=3068
这里有一种O(n)求解最长回文的算法Manacher算法。
这个算法有一个很巧妙的地方,它把奇数的回文串和偶数的回文串统一起来考虑了。这一点一直是在做回文串问题中时比较烦的地方。这个算法还有一个很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重复匹配。
算法大致过程是这样。先在每两个相邻字符中间插入一个分隔符,当然这个分隔符要在原串中没有出现过。一般可以用‘#’分隔。这样就非常巧妙的将奇数长度回文串与偶数长度回文串统一起来考虑了(见下面的一个例子,回文串长度全为奇数了),然后用一个辅助数组P记录以每个字符为中心的最长回文串的信息。P[id]记录的是以字符str[id]为中心的最长回文串,当以str[id]为第一个字符,这个最长回文串向右延伸了P[id]个字符。
原串: w aa b wsw f d
新串: # w # a # a # b # w # s # w # f # d #
辅助数组P: 1 2 1 2 3 2 1 2 1 2 1 4 1 2 1 2 1 2 1
这里有一个很好的性质,P[id]-1就是该回文子串在原串中的长度(包括‘#’)。如果这里不是特别清楚,可以自己拿出纸来画一画,自己体会体会。当然这里可能每个人写法不尽相同,不过我想大致思路应该是一样的吧。
好,我们继续。现在的关键问题就在于怎么在O(n)时间复杂度内求出P数组了。只要把这个P数组求出来,最长回文子串就可以直接扫一遍得出来了。
由于这个算法是线性从前往后扫的。那么当我们准备求P[i]的时候,i以前的P[j]我们是已经得到了的。我们用mx记在i之前的回文串中,延伸至最右端的位置。同时用id这个变量记下取得这个最优mx时的id值。(注:为了防止字符比较的时候越界,我在这个加了‘#’的字符串之前还加了另一个特殊字符‘$’,故我的新串下标是从1开始的)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<cmath>
#include<string>
#include<vector>
#include<algorithm>
#include<map>
#include<set>
#define maxn 250005
#define eps 1e-8
#define zero(a) fabs(a)<eps
using namespace std;
char str[maxn];
char s[maxn];
int p[maxn];
int Manacher(char *s,int n){
p[0]=p[1]=1;
int mx=1,id=1,ans=1;
for(int i=2;i<n;i++){
if(mx>i) p[i]=min(p[2*id-i],mx-i);
else p[i]=1;
for(;s[i-p[i]]==s[i+p[i]];p[i]++);
if(i+p[i]>mx){
mx=i+p[i];
id=i;
}
ans=max(ans,p[i]);
}
return ans-1;
}
int main(){
while(scanf("%s",str)!=EOF){
int l=strlen(str);
s[0]='@';
for(int i=0;i<l;i++){
s[2*i+1]='#';
s[2*i+2]=str[i];
}
s[2*l+1]='#';
printf("%d\n",Manacher(s,2*l+2));
}
return 0;
}