把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷2852】[USACO06DEC] Milk Patterns G(重拾后缀数组)

点此看题面

  • 给定一个长度为\(n\)的序列。
  • 求出最长的出现至少\(k\)次的子串的长度。
  • \(n\le2\times10^4\)

\(Height\)数组

考虑二分答案\(x\),则存在一个出现\(k\)次、长度为\(x\)的子串,等价于存在\(k\)个串的\(LCP\)长度大于等于\(x\)

贪心地考虑,必然选择排名连续的\(k\)个串。

根据\(SA\)\(Height\)数组的性质,\([l,r]\)\(LCP\)长度就是\(\min_{i=l+1}^rHeight_i\)

所以就是要判断是否存在连续\(k-1\)\(Height_i\)全都大于等于\(x\)

代码:\(O(nlogn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 20000
#define V 1000000
using namespace std;
int n,k,a[N+5];
class SuffxArray
{
	private:
		int SA[N+5],H[N+5],p[N+5],rk[N+5],t[V+5];
		I void Sort(CI S)
		{
			RI i;for(i=1;i<=S;++i) t[i]=0;for(i=1;i<=n;++i) ++t[rk[i]];
			for(i=1;i<=S;++i) t[i]+=t[i-1];for(i=n;i;--i) SA[t[rk[p[i]]]--]=p[i];
		}
	public:
		I void Init(CI n,int *a)//初始化
		{
			RI i,j,k;for(i=1;i<=n;++i) rk[p[i]=i]=a[i];
			RI t=0,S=V;for(Sort(S),k=1;t^n;k<<=1,S=t)//倍增预处理SA数组
			{
				for(i=1;i<=k;++i) p[i]=n-k+i;for(t=k,i=1;i<=n;++i) SA[i]>k&&(p[++t]=SA[i]-k);//按照第二维大小确定顺序
				for(Sort(S),i=1;i<=n;++i) p[i]=rk[i];for(rk[SA[1]]=t=1,i=2;i<=n;++i)
					(p[SA[i]]^p[SA[i-1]]||p[SA[i]+k]^p[SA[i-1]+k])&&++t,rk[SA[i]]=t;//求出新的排名
			}
			for(k=0,i=1;i<=n;++i) rk[SA[i]]=i;for(i=1;i<=n;++i)//预处理Height数组
			{
				if(k&&--k,rk[i]==1) continue;j=SA[rk[i]-1];//延续上一次的答案
				W(i+k<=n&&j+k<=n&&a[i+k]==a[j+k]) ++k;H[rk[i]]=k;//向外扩展
			}
		}
		I bool Check(CI x) {RI i,t=1;for(i=1;i<=n&&t<k;++i) H[i]>=x?++t:(t=1);return t==k;}//验证是否存在连续k-1位
}S;
int main()
{
	RI i;for(scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",a+i);
	S.Init(n,a);RI l=0,r=n,mid;W(l<r) S.Check(mid=l+r+1>>1)?l=mid:r=mid-1;//二分答案
	return printf("%d\n",l),0;
}
posted @ 2020-10-22 12:50  TheLostWeak  阅读(83)  评论(0编辑  收藏  举报