Description
给出一个长度为n的字符串,求所有连续重复子串中最大的重复次数。
n<=50000 数据组数<=20
Solution
这种题第一眼就应该想到SA。。。
然而SA并不是主要算法。一会再说。
我们枚举重复子串的长度l。那么很显然,这个子串会包含S[0],S[l],S[2l]…中的至少一个点。
我们枚举开头i=0,l,2l,3l….
然后求出k=lcp(i,i+l)。
这里的lcp(x,y)表示以x和y开头的后缀的lcp,可以用SA加上区间最小值求出。
那么重复次数就应该是(k/l+1)除法下整。
但是,开头不一定就是这些点,可能往左移一点,那么左移的距离就是(l-k%l)
然后再求出重复次数,取最大值就好了。
注意SA的常数。
Code
#include<cmath>
#include<cstdio>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define N 50005
using namespace std;
int n,ty,ans,ws[N],x[N],y[N],sa[N],rank[N],mi[15],f[N][15];
char s[N];
int min(int x,int y) {if (x<y) return x;else return y;}
int max(int x,int y) {if (x>y) return x;else return y;}
void tsort() {
int mx=0;
fo(i,1,n) mx=max(mx,x[y[i]]);
fo(i,1,mx) ws[i]=0;
fo(i,1,n) ws[x[y[i]]]++;
fo(i,1,mx) ws[i]+=ws[i-1];
fd(i,n,1) sa[ws[x[y[i]]]--]=y[i];
}
void getsa() {
fo(i,1,n) y[i]=i,sa[i]=0;tsort();
for(int j=1;j<=n;j*=2) {
int k=0;fo(i,n-j+1,n) y[++k]=i;
fo(i,1,n) if (sa[i]>j) y[++k]=sa[i]-j;
tsort();
fo(i,1,n) y[i]=x[i],x[i]=0;
x[sa[1]]=k=1;
fo(i,2,n) {
if (y[sa[i-1]]!=y[sa[i]]||y[sa[i-1]+j]!=y[sa[i]+j]) k++;
x[sa[i]]=k;
}
if (k==n) break;
}
fo(i,1,n) rank[sa[i]]=i;
}
void getheight() {
int k=0;
fo(i,1,n) {
if (k) k--;
int j=sa[rank[i]-1];
while (i+k<=n&&j+k<=n&&s[i+k]==s[j+k]) k++;
f[rank[i]][0]=k;
}
}
int lcp(int x,int y) {
x=rank[x];y=rank[y];int k;
if (x>y) k=x,x=y,y=k;x++;
int z=log(y-x+1)/log(2);
return min(f[x][z],f[y-mi[z]+1][z]);
}
int main() {
freopen("string9.in","r",stdin);
mi[0]=1;fo(i,1,14) mi[i]=mi[i-1]*2;
for(scanf("%d",&ty);ty;ty--) {
scanf("%d",&n);scanf("%s",s+1);
fo(i,1,n) x[i]=s[i]-'a';
getsa();getheight();ans=1;
fo(j,1,14) fo(i,1,n-mi[j-1])
f[i][j]=min(f[i][j-1],f[i+mi[j-1]][j-1]);
fo(l,1,n/2) fo(i,0,n/l-1) {
if ((i+1)*l+1>n||i*l+1>n) continue;
if ((n-i*l)/l+1<=ans) continue;
int k=lcp(i*l+1,(i+1)*l+1),r=l-k%l;
if (i*l+1-r>0) k=lcp(i*l+1-r,(i+1)*l+1-r);
ans=max(ans,k/l+1);
}
printf("%d\n",ans);
}
}