Description
给出一个(n(nleq2 imes10^4))个数的序列,求最长的出现了(k)次的子串的长度。
Solution
后缀数组+二分答案。
二分子串长度(len),若(h[x..y]geq len)则说明有一个长度为(len)的子串,分别在(sa[x-1..y])出现过共(y-x+2)次。所以只要找出最长的(hgeq len)的区间,对于每个(len)从头到尾扫一遍即可找出这个区间。
时间复杂度(O(nlogn))。
Code
//[USACO06DEC]牛奶模式Milk Patterns
#include <cstdio>
#include <cstring>
inline char gc()
{
static char now[1<<16],*s,*t;
if(s==t) {t=(s=now)+fread(now,1,1<<16,stdin); if(s==t) return EOF;}
return *s++;
}
inline int read()
{
int x=0; char ch=gc();
while(ch<'0'||'9'<ch) ch=gc();
while('0'<=ch&&ch<='9') x=x*10+ch-'0',ch=gc();
return x;
}
int max(int x,int y) {return x>y?x:y;}
int const N=2e4+10;
int n,m,s[N];
int sa[N],rnk[N<<1],h[N];
int cnt[N*50],tmp[N],rnk1[N<<1];
void getSA()
{
for(int i=1;i<=n;i++) cnt[s[i]]=1;
for(int i=1;i<=1e6;i++) cnt[i]+=cnt[i-1];
for(int i=1;i<=n;i++) rnk[i]=cnt[s[i]];
for(int L=1;L<=n;L<<=1)
{
memset(cnt,0,sizeof cnt);
for(int i=1;i<=n;i++) cnt[rnk[i+L]]++;
for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) tmp[cnt[rnk[i+L]]--]=i;
memset(cnt,0,sizeof cnt);
for(int i=1;i<=n;i++) cnt[rnk[tmp[i]]]++;
for(int i=1;i<=n;i++) cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--) sa[cnt[rnk[tmp[i]]]--]=tmp[i];
int k=0; memcpy(rnk1,rnk,sizeof rnk);
for(int i=1;i<=n;i++)
{
if(rnk1[sa[i]]!=rnk1[sa[i-1]]||rnk1[sa[i]+L]!=rnk1[sa[i-1]+L]) k++;
rnk[sa[i]]=k;
}
if(k>=n) break;
}
for(int i=1,k=0;i<=n;i++)
{
if(rnk[i]==1) {h[1]=0; continue;}
if(k) k--;
while(s[i+k]==s[sa[rnk[i]-1]+k]) k++;
h[rnk[i]]=k;
}
}
int check(int x)
{
int res=0;
for(int i=1,fr=1;i<=n+1;i++)
if(h[i]<x) res=max(res,i-fr+1),fr=i+1;
return res;
}
int main()
{
n=read(),m=read(); for(int i=1;i<=n;i++) s[i]=read();
getSA();
int L=1,R=n;
while(L<=R)
{
int mid=L+R>>1;
if(check(mid)>=m) L=mid+1;
else R=mid-1;
}
printf("%d
",R);
return 0;
}
P.S.
注意在check(x)
中我循环到了(n+1),否则统计不到处于末尾的(hgeq len)的区间。
同权限题BZOJ1717。