字符串(马拉车算法,后缀数组,稀疏表):BZOJ 3676 [Apio2014]回文串

Description

考虑一个只包含小写拉丁字母的字符串s。我们定义s的一个子串t的“出
现值”为t在s中的出现次数乘以t的长度。请你求出s的所有回文子串中的最
大出现值。

Input

输入只有一行,为一个只包含小写字母(a -z)的非空字符串s。

Output


输出一个整数,为逝查回文子串的最大出现值。

Sample Input

【样例输入l】
abacaba

【样例输入2]
www

Sample Output

【样例输出l】
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行是有顺序的,写反了会死循环。

尽最大的努力,做最好的自己!
原文地址:https://www.cnblogs.com/TenderRun/p/5285144.html