题目:给出一个串,求重复次数最多的连续重复子串
枚举长度为L,然后看长度为L的字符串最多连续出现几次。
既然长度为L的串重复出现,那么str[0],str[l],str[2*l]……中肯定有两个连续的出现在字符串中。
那么就枚举连续的两个,然后从这两个字符前后匹配,看最多能匹配多远。
即以str[i*l],str[i*l+l]前后匹配,这里是通过查询suffix(i*l),suffix(i*l+l)的最长公共前缀
通过rank值能找到i*l,与i*l+l的排名,我们要查询的是这段区间的height的最小值,通过RMQ预处理
达到查询为0(1)的复杂度,
设LCP长度为M, 则答案显然为M / L + 1, 但这不一定是最好的, 因为答案的首尾不一定再我们枚举的位置上. 我的解决方法是, 我们考虑M % L的值的意义, 我们可以认为是后面多了M % L个字符, 但是我们更可以想成前面少了(L - M % L)个字符! 所以我们求后缀j * L - (L - M % L)与后缀(j + 1) * L - (L - M % L)的最长公共前缀。
即把之前的区间前缀L-M%L即可。
然后把可能取到最大值的长度L保存,由于 题目要求字典序最小,通过sa数组进行枚举,取到的第一组,肯定是字典序最小的。
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;
const int N=2*100100;
int cl,rk[N],sa[N],Rs[N],y[N],wr[N],h[N],r[N][30];
char c[N];
int minn(int x,int y){return x<y ? x:y;}
void get_sa(int m)
{
for(int i=1;i<=cl;i++) rk[i]=c[i]-'a'+1;
for(int i=1;i<=m;i++) Rs[i]=0;
for(int i=1;i<=cl;i++) Rs[rk[i]]++;
for(int i=1;i<=m;i++) Rs[i]+=Rs[i-1];
for(int i=cl;i>=1;i--) sa[Rs[rk[i]]--]=i;
int ln=1,p=0;
while(p<cl)
{
int k=0;
for(int i=cl-ln+1;i<=cl;i++) y[++k]=i;
for(int i=1;i<=cl;i++) if(sa[i]>ln) y[++k]=sa[i]-ln;
for(int i=1;i<=cl;i++) wr[i]=rk[y[i]];
for(int i=1;i<=m;i++) Rs[i]=0;
for(int i=1;i<=cl;i++) Rs[wr[i]]++;
for(int i=1;i<=m;i++) Rs[i]+=Rs[i-1];
for(int i=cl;i>=1;i--) sa[Rs[wr[i]]--]=y[i];
for(int i=1;i<=cl;i++) wr[i]=rk[i];
for(int i=cl+1;i<=cl+ln;i++) wr[i]=0;
p=1;rk[sa[1]]=1;
for(int i=2;i<=cl;i++)
{
if(wr[sa[i]]!=wr[sa[i-1]] || wr[sa[i]+ln]!=wr[sa[i-1]+ln]) p++;
rk[sa[i]]=p;
}
ln*=2,m=p;
}
sa[0]=0,rk[0]=0;
}
void get_h()
{
int k=0,j;
for(int i=1;i<=cl;i++) if(rk[i]!=1)
{
j=sa[rk[i]-1];
if(k) k--;
while(c[j+k]==c[i+k] && j+k<=cl && i+k<=cl) k++;
h[rk[i]]=k;
}
h[1]=0;
}
void get_rmq()
{
for(int i=1;i<=cl;i++) r[i][0]=h[i];
for(int j=1;(1<<j)<=cl;j++)
for(int i=1;i+(1<<j)-1<=cl;i++)
{
r[i][j]=minn(r[i][j-1],r[i+(1<<(j-1))][j-1]);
}
}
int query_rmq(int i,int j)
{
if(i>j) swap(i,j);
i++;
int k=0;
while(i+(1<<(k+1)) <= j) k++;
return minn(r[i][k],r[j-(1<<k)+1][k]);
}
int main()
{
int x,y,z,t0,t1,now,ans,al,ar,T=0;
while(1)
{
scanf("%s",c+1);
cl=strlen(c+1);
if(cl==1 && c[1]=='#') return 0;
printf("Case %d: ",++T);
get_sa(30);
get_h();
get_rmq();
ans=0;al=ar=0;
for(int L=1;L*2<=cl;L++) //枚举L 一个子串的长度
{
for(int i=0;L*(i+1)+1<=cl;i++)
{
x=L*i+1,y=L*(i+1)+1;
if(c[x]!=c[y]) continue; //找到相邻L的相等的字符
z=query_rmq(rk[x],rk[y]); // 找最长公共前缀
t1=y+z-1;
t0=0;
for(int j=0;j<=L-1;j++)//往前匹配
{
if(x-j<1 || c[x-j]!=c[y-j]) break;
t0=x-j;
now=((t1-t0+1)/L);//字串的长度
if(now>ans || (now==ans && rk[t0]<rk[al]))
ans=now,al=t0,ar=t0+now*L-1;
}
}
}
if(ans==0) printf("%c\n",c[sa[1]]);
else
{
for(int i=al;i<=ar;i++) printf("%c",c[i]);printf("\n");
}
}
return 0;
}