POJ3693 - Maximum repetition substring
Description
给出一个字符串\(s\),求\(s\)的一个循环子串,其循环次数最多。
Solution
容易知道\(\left \lfloor \dfrac{lcp(s_{i..n},s_{i+L..n})}{L} \right \rfloor+1\)就等于从\(s_i\)开始的长度为\(L\)的串的循环次数。
\[\begin{align*}
s \quad& cababacbacbacbaca \\
i=2 \quad& .aba............. \\
i+L=4 \quad& ...aba........... \\
i=4 \quad& .....acbacbac.... \\
i+L=7 \quad& ........acbacbac. \\
\end{align*}\]
枚举循环节的长度\(L\)。如果有一个长度为\(L\)的子串连续出现,那么它至少会覆盖\(s_1,s_{L+1},s_{2L+1}...\)中的连续两个。那么我们记录\(s_{tL+1}\)与\(s_{(t+1)L+1}\)向前匹配的长度为\(x\),向后匹配的长度为\(y\)。那么循环串可能以\(s_{i-x}..s_{i-x+(y-x)\%L}\)中任意一个作为开头循环\(\dfrac{x+y}{L}+1\)次,用RMQ求出区间最小的\(rnk\)即可更新答案。
Code
//Maximum repetition substring
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
int const N=1e5+10;
int n;
char s[N];
int sa[N],rnk[N<<1],h[N];
int cnt[N],tmp[N],rnk1[N<<1];
int rmq[N<<1][20];
void getSA()
{
memset(rnk,0,sizeof rnk);
memset(cnt,0,sizeof cnt);
for(int i=1;i<=n;i++) cnt[s[i]]=1;
for(int i=1;i<=256;i++) cnt[i]+=cnt[i-1];
for(int i=1;i<=n;i++) rnk[i]=cnt[s[i]];
for(int L=1,k=0;k<n;L<<=1)
{
memset(cnt,0,sizeof cnt);
for(int i=1;i<=n;i++) cnt[rnk[i+L]]++;
for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) tmp[cnt[rnk[i+L]]--]=i;
memset(cnt,0,sizeof cnt);
for(int i=1;i<=n;i++) cnt[rnk[tmp[i]]]++;
for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) sa[cnt[rnk[tmp[i]]]--]=tmp[i];
k=0; memcpy(rnk1,rnk,sizeof rnk1);
for(int i=1;i<=n;i++)
{
if(rnk1[sa[i]]!=rnk1[sa[i-1]]||rnk1[sa[i]+L]!=rnk1[sa[i-1]+L]) k++;
rnk[sa[i]]=k;
}
}
for(int i=1,k=0;i<=n;i++)
{
if(rnk[i]==1) {h[1]=k=0; continue;}
if(k) k--;
while(s[i+k]==s[sa[rnk[i]-1]+k]) k++;
h[rnk[i]]=k;
}
memset(rmq,0,sizeof rmq);
for(int i=1;i<=n;i++) rmq[i][0]=h[i];
for(int k=1;(1<<k)<=n;k++)
for(int i=1;i<=n;i++)
rmq[i][k]=min(rmq[i][k-1],rmq[i+(1<<k-1)][k-1]);
}
int lcp(int i,int j)
{
if(i==j) return n-i+1;
int x=rnk[i],y=rnk[j],t=0;
if(x>y) swap(x,y); x++;
while((1<<t)<=y-x+1) t++; t--;
return min(rmq[x][t],rmq[y-(1<<t)+1][t]);
}
int ansS,ansT,ansL;
int main()
{
for(int task=1;true;task++)
{
memset(s,0,sizeof s);
scanf("%s",s+1); if(s[1]=='#') break;
n=strlen(s+1); getSA();
ansS=1,ansT=1,ansL=n;
for(int L=1;L<=n;L++)
for(int i=1;i+L<=n;i+=L)
{
int j=i+L,len=lcp(i,j);
for(int k=0;i-k>0&&s[i-k]==s[j-k]&&k<L;k++)
{
int t=(len+k)/L+1;
if(t>ansT) ansS=i-k,ansT=t,ansL=L;
else if(t==ansT&&rnk[i-k]<rnk[ansS]) ansS=i-k,ansT=t,ansL=L;
}
}
printf("Case %d: ",task);
for(int i=1;i<=ansT*ansL;i++) printf("%c",s[ansS+i-1]);
puts("");
}
return 0;
}
P.S.
我没写RMQ,也能AC原题;但是\(aaaa..aa\)就能hack我这个程序。