洛谷P2852 - [USACO06DEC]牛奶模式Milk Patterns
Description
给出一个\(n(n\leq2\times10^4)\)个数的序列,求最长的出现了\(k\)次的子串的长度。
Solution
后缀数组+二分答案。
二分子串长度\(len\),若\(h[x..y]\geq len\)则说明有一个长度为\(len\)的子串,分别在\(sa[x-1..y]\)出现过共\(y-x+2\)次。所以只要找出最长的\(h\geq 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\n",R);
return 0;
}
P.S.
注意在check(x)
中我循环到了\(n+1\),否则统计不到处于末尾的\(h\geq len\)的区间。
同权限题BZOJ1717。