题目:
题意:
给定一个字符串,求重复次数最多的连续重复子串,并输出字典序最小的一个。
题解:
先枚举长度 L ,然后求长度为 L 的子串最多能连续出现几次。首先连续出现 1 次是肯定可以的,所以这里只考虑至少 2 次的情况。假设在原字符串中连续出现 2 次,记这个子字符串为 S ,那么 S 肯定包括了字符 r[0], r[L], r[L*2], r[L*3], …… 中的某相邻的两个。
所以先看字符 r[L*i] 和 r[L*(i+1)] 的lcp,意为向后最多能匹配多远,然后往前扩展。但如果有一种情况就是再多一些前面的和后面的才能形成一个新的怎么办呢?我们可以等移动到后面去的时候再往前扩展。记这个总长度为 K ,那么这里连续出现了 K/L+1 次。最后看最大值是多少。
两个后缀的最长公共前缀是它们 height 数组的区间最小值,用rmq实现O(1)查询。
时间复杂度为
O(n/1+n/2+n/3+...+n/n)=O(nlogn)
判断字典序最小怎么办呢?直接用我们求出来的rank判断!
代码:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=100005;
int c[N],X[N],n,sa[N],Y[N],rank[N],height[N],f[N][25],*x,*y;char s[N];
void build_sa()
{
int m=300; x=X; y=Y;
for (int i=0; i<m; i++) c[i]=0;
for (int i=0; i<n; i++) c[x[i]=s[i]]++;
for (int i=1; i<m; i++) c[i]+=c[i-1];
for (int i=n-1; i>=0; i--) sa[--c[x[i]]]=i;
for (int k=1;k<=n;k<<=1){
int p=0;
for (int i=n-k;i<n;i++) y[p++]=i;
for (int i=0;i<n;i++)
if (sa[i]>=k) y[p++]=sa[i]-k;
for (int i=0;i<m;i++) c[i]=0;
for (int i=0;i<n;i++) ++c[x[y[i]]];
for (int i=1;i<m;i++) c[i]+=c[i-1];
for (int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1; x[sa[0]]=0;
for (int i=1;i<n;i++) x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&
((sa[i-1]+k>=n?-1:y[sa[i-1]+k])==(sa[i]+k>=n?-1:y[sa[i]+k]))?p-1:p++;
if (p>n) break;
m=p;
}
}
void build_height()
{
for (int i=0;i<n;i++) rank[sa[i]]=i;
height[0]=0;
int k=0;
for (int i=0;i<n;i++)
{
if (!rank[i]) continue;
if (k) --k;
int j=sa[rank[i]-1];
while (i+k<n && j+k<n && s[i+k]==s[j+k]) ++k;
height[rank[i]]=k;
}
}
void rmq()
{
for (int i=0;i<n;i++) f[i][0]=height[i];
for (int i=1;i<20;i++)
for (int j=0;j<n;j++)
if (j+(1<<i)-1<n) f[j][i]=min(f[j][i-1],f[j+(1<<i-1)][i-1]);
else break;
}
int lcp(int a,int b)
{
int l=rank[a],r=rank[b];
if (r<l) swap(l,r);l++;
int k=log2(r-l+1);
return min(f[l][k],f[r-(1<<k)+1][k]);
}
int main()
{
int id=0;
while (scanf("%s",s) && s[0]!='#')
{
n=strlen(s);
build_sa();
build_height();
rmq();
int ans=0,sum,al=0,ar,t0,t1,wz,ll;
for (int len=1;len*2<=n;len++)
for (int i=0;len*(i+1)<n;i++)
{
t0=i*len; t1=(i+1)*len;
if (s[t0]!=s[t1]) continue;
ll=t0+lcp(t0,t1)-1;
for (int j=0;j<=len-1;j++)
{
if (t0-j<0 || s[t0-j]!=s[t1-j]) break;
wz=t0-j;
sum=(ll-wz+1)/len+1;
if (ans<sum || (ans==sum && rank[al]>rank[wz])) ans=sum,al=wz,ar=wz+ans*len-1;
}
}
printf("Case %d: ",++id);
if (!ans) printf("%c",s[sa[0]]);
else for (int i=al;i<=ar;i++) printf("%c",s[i]);
printf("\n");
}
}

本文介绍了一种寻找字符串中重复次数最多且字典序最小的连续子串算法。通过枚举子串长度并利用后缀数组和最长公共前缀概念,实现高效查找。最终算法的时间复杂度为O(nlogn)。
1400

被折叠的 条评论
为什么被折叠?



