原题:http://poj.org/problem?id=3261
题解:求出现至少k次子串。后缀数组可以处理子串的问题。后缀数组主要的定义如下。
rank[i] 表示第i个后缀的排名
sa[i] 表示排名为i的哪个后缀
h[i] 表示排名为i与i+1的最长公共前缀
排名指字典序,可以用基数排序,倍增预处理上面3个数组。
出现了K次的子串长度就是h数组连续k-1的最小值,我们要求连续k-1的最小值的最大值,可以枚举左端点求RMQ,求区间最小值有很多方法,这道题可以用ST表,或数列分块的方法,这里用的是数列分块的方法。
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N=22000;
const int M=1e6+10;
int n,m,kk,s[N],suf[N],sa[N],pos[N],rank[N<<1]={0},rank1[N<<1]={0},cnt[M],tmp[M],t[M];
int h[N],v[1100];
inline int query(int l,int r){
int ans=1e18;
for(int i=l;i<=min(r,pos[l]*m);i++) ans=min(ans,h[i]);
if(pos[l] != pos[r])
for(int i=(pos[r]-1)*m+1;i<=r;i++)ans=min(ans,h[i]);
for(int i=pos[l]+1;i<=pos[r]-1;i++) ans=min(ans,v[i]);
return ans;
}
int main(){
freopen("poj3261.in","r",stdin);
scanf("%d %d",&n,&kk);
for(int i=1;i<=n;i++) scanf("%d",&s[i]);
s[0]=-1;s[n+1]=-1;
//初始化 rank[1],首字母排名
//以经确定的名次
for(int i=1;i<=n;i++) t[ s[i] ]++;
for(int i=0;i<=1e6;i++) t[i]+=t[i-1];
for(int i=1;i<=n;i++) rank[i]=t[s[i]];
for(int p=1,k=0; k!=n ; p<<=1){
//对第一关键字排序
memset(cnt,0,sizeof cnt);
for(int i=1;i<=n;i++) cnt[ rank[i+p] ]++;
for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) tmp[cnt[rank[i+p]]--]=i;//收集
//对第二关键字排序
memset(cnt,0,sizeof cnt);
for(int i=1;i<=n;i++) cnt[rank[i]]++;
for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) sa[cnt[rank[tmp[i]]]--]=tmp[i];
//通过sa求新的rank
memcpy(rank1,rank,sizeof(rank)>>1);
k=1;
rank[ sa[1] ]=1;
for(int i=2;i<=n;i++){
if(rank1[ sa[i] ] != rank1[sa[i-1]] || rank1[sa[i]]==rank1[sa[i-1]] && rank1[sa[i]+p] != rank1[sa[i-1]+p]) k++;
rank[ sa[i] ]=k;
}
}
//求height i,i+1
for(int i=1,k=1;i<=n;i++){
if(rank[i]==n) {
h[rank[i]]=0;continue;
}
k--;if(k<0)k=0;
while(s[i+k]==s[ sa[rank[i]+1]+k] )k++;
h[rank[i]]=k;
}
//分块的预处理
m=sqrt(n);
for(int i=1;i<=n;i++) pos[i]=(i-1)/m+1;
for(int i=1;i<=pos[n];i++) v[i]=1e18;
for(int i=1;i<=n;i++) v[ pos[i] ]=min(v[pos[i]],h[i]);
int L=kk-1;int ans=0;
for(int i=1;i<=n-L+1;i++){
int x=i;int y=x+L-1;
ans=max(ans,query(x,y));
}
printf("%d\n",ans);
return 0;
}