后缀数组 二分
题目大意:给你一个数m和一个字符串s,求长度最大的子串ss满足它在s中的出现次数 ≥ ≥ m。输出ss的长度及最后出现的位置。
首先二分答案,设长度为x,预处理出h数组后,统计连续一段 h[i]≥ h [ i ] ≥ x的i的个数。如果个数 ≥ ≥ m就满足了,并记录最后一个sa[i]。
还要特判一下m=1的情况。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 40005
using namespace std;
int n,m,k,lst,sa[N],rk[N],t[N],rs[N],h[N];
char s[N];
void RS(){
memset(rs,0,sizeof(rs));
for (int i=1;i<=n;i++) rs[rk[t[i]]]++;
for (int i=1;i<=m;i++) rs[i]+=rs[i-1];
for (int i=n;i>=1;i--) sa[rs[rk[t[i]]]--]=t[i];
}
void SA(){
m=122,n=strlen(s+1);
for (int i=1;i<=n;i++) rk[i]=s[i],t[i]=i; RS();
for (int j=1,p=0;j<=n;j<<=1,p=0){
for (int i=n-j+1;i<=n;i++) t[++p]=i;
for (int i=1;i<=n;i++) if (sa[i]>j) t[++p]=sa[i]-j;
RS(),memcpy(t,rk,sizeof(t)),p=1,rk[sa[1]]=1;
for (int i=2,f;i<=n;i++){
f=t[sa[i]]==t[sa[i-1]]&&t[sa[i]+j]==t[sa[i-1]+j];
rk[sa[i]]=f?p:++p;
}
if (p==n) break; m=p;
}
}
void mkh(){
memset(h,0,sizeof(h));
for (int i=1,k=0;i<=n;i++){
if (k) k--;
while (s[i+k]==s[sa[rk[i]-1]+k]) k++;
h[rk[i]]=k;
}
}
int pd(int x){
lst=0; int sum=1,mx=0;
for (int i=1;i<=n;i++){
if (h[i]>=x) sum++,mx=max(mx,max(sa[i],sa[i-1]));
else sum=1,mx=0;
if (sum>=k) lst=max(lst,mx);
}
return lst;
}
int main(){
while(~scanf("%d",&k)&&k){
memset(s,0,sizeof(s));
scanf("%s",s+1),SA(),mkh();
if (k==1){
printf("%d 0\n",strlen(s+1));
continue;
}
int l=0,r=n,md,ans=0,x;
while (l<=r)
if (pd(md=l+r>>1)) ans=md,x=lst,l=md+1;
else r=md-1;
if (ans) printf("%d %d\n",ans,x-1);
else puts("none");
}
return 0;
}