Description
考虑一个只包含小写拉丁字母的字符串s。我们定义s的一个子串t的“出
现值”为t在s中的出现次数乘以t的长度。请你求出s的所有回文子串中的最
大出现值。
Input
输入只有一行,为一个只包含小写字母(a -z)的非空字符串s。
Output
输出一个整数,为逝查回文子串的最大出现值。
Sample Input
【样例输入l】
abacaba
【样例输入2]
www
abacaba
【样例输入2]
www
Sample Output
【样例输出l】
7
【样例输出2]
4
7
【样例输出2]
4
HINT
一个串是回文的,当且仅当它从左到右读和从右到左读完全一样。
在第一个样例中,回文子串有7个:a,b,c,aba,aca,bacab,abacaba,其中:
● a出现4次,其出现值为4:1:1=4
● b出现2次,其出现值为2:1:1=2
● c出现1次,其出现值为l:1:l=l
● aba出现2次,其出现值为2:1:3=6
● aca出现1次,其出现值为1=1:3=3
●bacab出现1次,其出现值为1:1:5=5
● abacaba出现1次,其出现值为1:1:7=7
故最大回文子串出现值为7。
【数据规模与评分】
数据满足1≤字符串长度≤300000。
这道题要求出每一个回文串,以及出现的次数。
先用马拉车算法求出每个位置最长的回文子串,可以证明:从每个位置上取出最长的回文串去枚举一定能枚举出最优解。
接着对于,每个位置的最长回文子串用SA与ST快速求出其出现的次数,然而这并非正解,回文自动机才是(话说APIO2014的时候还没有这玩意儿),那时这个算法应该是最优的了。
1 #include <iostream> 2 #include <cstring> 3 #include <cstdio> 4 using namespace std; 5 const int maxn=300100; 6 char s[maxn],S[maxn<<1]; 7 int maxl[maxn<<1]; 8 int pos[maxn<<1],maxL[maxn]; 9 int rank[maxn],Wa[maxn],Wb[maxn],Wv[maxn],Ws[maxn],sa[maxn],r[maxn]; 10 int lcp[maxn],len; 11 int mm[maxn],Min[maxn][32]; 12 bool cmp(int *p,int a,int b,int l){ 13 return p[a]==p[b]&&p[a+l]==p[b+l]; 14 } 15 16 void DA(int n,int m){ 17 int i,j,p,*x=Wa,*y=Wb,*t; 18 for(i=0;i<m;i++)Ws[i]=0; 19 for(i=0;i<n;i++)++Ws[x[i]=r[i]]; 20 for(i=1;i<m;i++)Ws[i]+=Ws[i-1]; 21 for(i=n-1;i>=0;i--)sa[--Ws[x[i]]]=i; 22 23 for(j=1,p=1;p<n;j<<=1,m=p){ 24 for(p=0,i=n-j;i<n;i++)y[p++]=i; 25 for(i=0;i<n;i++) 26 if(sa[i]>=j) 27 y[p++]=sa[i]-j; 28 29 for(i=0;i<m;i++)Ws[i]=0; 30 for(i=0;i<n;i++)++Ws[Wv[i]=x[y[i]]]; 31 for(i=1;i<m;i++)Ws[i]+=Ws[i-1]; 32 for(i=n-1;i>=0;i--)sa[--Ws[Wv[i]]]=y[i]; 33 for(t=x,x=y,y=t,x[sa[0]]=0,i=1,p=1;i<n;i++) 34 x[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++; 35 } 36 } 37 38 void Lcp(int n){ 39 int i,j,k=0; 40 for(i=1;i<=n;i++)rank[sa[i]]=i; 41 for(i=0;i<n;lcp[rank[i++]]=k) 42 for(k?--k:k,j=sa[rank[i]-1];r[j+k]==r[i+k];++k); 43 } 44 int Qmin(int l,int r){ 45 return min(Min[l][mm[r-l+1]],Min[r-(1<<mm[r-l+1])+1][mm[r-l+1]]); 46 } 47 48 int Query(int p,int l){ 49 int ret=1,lo,hi; 50 hi=rank[p];lo=0; 51 while(lo<=hi){ 52 int mid=(lo+hi)>>1; 53 if(Qmin(mid,rank[p])>=l)hi=mid-1; 54 else lo=mid+1; 55 } 56 ret+=rank[p]-lo+1; 57 58 hi=len;lo=rank[p]+1; 59 while(lo<=hi){ 60 int mid=(lo+hi)>>1; 61 if(Qmin(rank[p]+1,mid)>=l)lo=mid+1; 62 else hi=mid-1; 63 } 64 return ret+hi-rank[p]; 65 } 66 67 int main(){ 68 scanf("%s",s); 69 len=strlen(s); 70 int l=0; 71 S[l++]='#';S[l++]='&'; 72 for(int i=0;i<len;i++){ 73 pos[l]=i; 74 S[l++]=s[i]; 75 pos[l]=i+1; 76 S[l++]='&'; 77 } 78 S[l]=0; 79 int mx=0,id; 80 for(int i=1;i<l;i++){ 81 maxl[i]=mx>i?min(maxl[id*2-i],mx-i):1; 82 while(S[i+maxl[i]]==S[i-maxl[i]])maxl[i]++; 83 if(i+maxl[i]>mx){ 84 mx=i+maxl[i]; 85 id=i; 86 } 87 } 88 89 for(int i=1;i<=l;i++) 90 maxL[pos[i-maxl[i]+1]]=max(maxL[pos[i-maxl[i]+1]],maxl[i]-1); 91 //maxL[i]原s字符串中i位置最长回文子串 92 for(int i=0;i<len;i++)r[i]=s[i]; 93 DA(len+1,128); 94 Lcp(len); 95 96 mm[0]=-1; 97 for(int i=1;i<=len;i++){ 98 mm[i]=(i&(i-1))?mm[i-1]:mm[i-1]+1; 99 Min[i][0]=lcp[i]; 100 } 101 102 for(int k=1;k<=mm[len];k++) 103 for(int i=1;i+(1<<k)-1<=len;i++) 104 Min[i][k]=min(Min[i][k-1],Min[i+(1<<(k-1))][k-1]); 105 106 long long ans=0; 107 for(int i=0;i<len;i++){ 108 ans=max(ans,1ll*maxL[i]*Query(i,maxL[i])); 109 } 110 printf("%lld ",ans); 111 return 0; 112 }
然后还有一种方法,前面提过,回文自动机:
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 const int maxn=300010; 5 int last,cnt,ch[maxn][26],fail[maxn],len[maxn],num[maxn]; 6 long long ans=0; 7 char s[maxn]; 8 int find(int i,int x){ 9 while(s[i-len[x]-1]!=s[i])x=fail[x]; 10 return x; 11 } 12 int main(){ 13 scanf("%s",s); 14 fail[0]=1;len[1]=-1;cnt=1; 15 for(int i=0;s[i];i++){ 16 int j=find(i,last); 17 if(ch[j][s[i]-'a']) 18 last=ch[j][s[i]-'a']; 19 else{ 20 len[last=++cnt]=len[j]+2; 21 fail[last]=ch[find(i,fail[j])][s[i]-'a']; 22 ch[j][s[i]-'a']=last; 23 } 24 num[last]++; 25 } 26 for(int i=cnt;i>=0;i--) 27 ans=max(ans,1ll*num[i]*len[i]),num[fail[i]]+=num[i]; 28 printf("%lld ",ans); 29 return 0; 30 }
超级短,超级简单,但还是有些地方要注意:
14行的初始化不能乱改,改了挺麻烦的;21,22行是有顺序的,写反了会死循环。