【POJ3261】Milk Patterns-后缀数组+二分答案

测试地址:Milk Patterns

题目大意:求数列中至少出现K次的重复子串的最长长度。

做法:和重复子串有关的题目,一下子就想到后缀数组。先对数列求一遍后缀数组,再求出height数组。我们发现答案是具有单调性的,所以我们二分答案M,将排序后的后缀序列按height数组分成几个连续区间,如果height[i]<M就把SA[i-1]与SA[i]划分在两个区间内。然后观察每个区间内的后缀个数,如果一个区间内后缀个数≥K,则说明存在长度为M的出现至少K次的重复子串,如果每个区间内都没有则说明不存在。

以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,k,a[20010],x[50010]={0},y[20010],s[20010],t[1000010];
int rank[20010],sa[20010],height[20010];

void DA()
{
  int up=1000000,p=1;
  for(int i=1;i<=n;i++) x[i]=a[i];
  while(p<n)
  {
    for(int i=1;i<=n;i++) y[i]=x[i+p];
	
	memset(t,0,sizeof(t));
	for(int i=1;i<=n;i++) t[y[i]]++;
	for(int i=1;i<=up;i++) t[i]+=t[i-1];
	for(int i=up;i>=1;i--) t[i]=t[i-1];
	t[0]=0;
	for(int i=1;i<=n;i++) s[++t[y[i]]]=i;
	
	memset(t,0,sizeof(t));
	for(int i=1;i<=n;i++) t[x[s[i]]]++;
	for(int i=1;i<=up;i++) t[i]+=t[i-1];
	for(int i=up;i>=1;i--) t[i]=t[i-1];
	t[0]=0;
	for(int i=1;i<=n;i++) y[++t[x[s[i]]]]=s[i];
	
	up=1;
	s[y[1]]=1;
	for(int i=2;i<=n;i++)
	{
	  if (x[y[i]]!=x[y[i-1]]||x[y[i]+p]!=x[y[i-1]+p]) up++;
	  s[y[i]]=up;
	}
	
	for(int i=1;i<=n;i++) x[i]=s[i];
    p<<=1;
  }
  for(int i=1;i<=n;i++)
  {
    rank[i]=x[i];
	sa[rank[i]]=i;
  }
}

void calc_height()
{
  for(int i=1,j=0;i<=n;i++)
  {
    if (rank[i]==1) continue;
	while(a[i+j]==a[sa[rank[i]-1]+j]) j++;
	height[rank[i]]=j;
	if (j>0) j--;
  }
  height[n+1]=0;
}

bool check(int m)
{
  int start=1;
  for(int i=1;i<=n;i++)
  {
    if (height[i+1]<m)
	{
	  if (i-start+1>=k) return 1;
	  start=i+1;
	}
  }
  return 0;
}

int main()
{
  scanf("%d%d",&n,&k);
  for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
  DA();
  calc_height();
  
  int l=0,r=n,ans=0;
  while(l<=r)
  {
    int mid=(l+r)>>1;
	if (check(mid))
	{
	  ans=max(ans,mid);
	  l=mid+1;
	}
	else r=mid-1;
  }
  
  printf("%d",ans);
  return 0;
}


原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793769.html