POJ 1743 Musical Theme(后缀数组+二分答案)

题意

长度为 \(n\) 由数字构成的串,求一段最长满足以下要求的子串:

  • 长度至少为 \(5\)
  • 在其他位置有一个不相交的等长子串满足原串或原串加上或减去一个数后与之完全相同。

\(1\leq n \leq 20000\)

思路

不难看出题目要求的是满足两两数之差相同的串,那么直接对原串进行差分,剩下的就是求两个串至少间隔为 \(1\) 的相等串的最长长度。这显然是满足单调性的,那么二分这个长度,然后扫后缀数组,对于每一个 \(H\) 大于等于这个长度的区间,看一看有没有符合要求的串,具体实现看代码。

代码

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
template<typename T,typename _T>inline bool chk_min(T &x,const _T y){return y<x?x=y,1:0;}
template<typename T,typename _T>inline bool chk_max(T &x,const _T y){return x<y?x=y,1:0;}
typedef long long ll;
const int N=2e4+5;
int sa[N],rk[N],H[N],tmp[3][N];
int s[N];
int n;

void get_SA(int *s,int n,int m)
{
	int *x=tmp[0],*y=tmp[1],*c=tmp[2];
	x[n+1]=y[n+1]=0;
	FOR(i,1,m)c[i]=0;
	FOR(i,1,n)c[x[i]=s[i]]++;
	FOR(i,2,m)c[i]+=c[i-1];
	DOR(i,n,1)sa[c[x[i]]--]=i;
	for(int k=1;k<=n;k<<=1)
	{
		int p=0;
		FOR(i,n-k+1,n)y[++p]=i;
		FOR(i,1,n)if(sa[i]>k)y[++p]=sa[i]-k;
		FOR(i,1,m)c[i]=0;
		FOR(i,1,n)c[x[y[i]]]++;
		FOR(i,2,m)c[i]+=c[i-1];
		DOR(i,n,1)sa[c[x[y[i]]]--]=y[i];
		std::swap(x,y);
		p=x[sa[1]]=1;
		FOR(i,2,n)x[sa[i]]=y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]?p:++p;
		if(p==n)break;
		m=p;
	}
	FOR(i,1,n)rk[sa[i]]=i;
	int k=0;
	FOR(i,1,n)
	{
		if(k)k--;
		if(rk[i]==1)continue;
		int j=sa[rk[i]-1];
		while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])k++;
		H[rk[i]]=k;
	}
}

bool check(int k)
{
	int Mx=-1e9,Mi=1e9;
	FOR(i,1,n)
	{
		chk_max(Mx,sa[i]),chk_min(Mi,sa[i]);
		if(Mx-Mi>=k+1)return true;
		if(H[i+1]<k)Mx=-1e9,Mi=1e9;
	}
	return false;
}

int main()
{
	while(scanf("%d",&n),n)
	{
		FOR(i,1,n)scanf("%d",&s[i]);
		FOR(i,1,n-1)s[i]=s[i+1]-s[i]+100;
		n--;
		get_SA(s,n,200);
		int l=3,r=n;
		while(l<r)
		{
			int mid=(l+r+1)>>1;
			if(check(mid))
				l=mid;
			else r=mid-1;
		}
		printf("%d\n",l>=4?l+1:0);
	}
	return 0;
}
posted @ 2019-03-21 17:30  Paulliant  阅读(87)  评论(0编辑  收藏