[POJ 1743]Musical Theme

[POJ 1743]Musical Theme

\(Description:\)

一个长度为n的数列,问是否存在长度大于等于5的相似不重叠子列

若存在则求最长的不重叠的相似子列,若不存在则输出0

相似的定义为相应位置的差值为常数(如:1,2,5和3,4,6相似)

\(Solution:\)

将原数组进行差分,这样相似的子列就变成了差分数组中相同的子列

原题目就变成了在差分数组中求最长的不重叠的相等子列

注意,差分为相邻两个数的差值,因此差分后数组的长度为n-1,且原数组中长度为x的相似子列对应差分数组中长度为x-1的相等子列

如果不考虑是否重叠的问题,那么这是一个经典的LCP(最长公共前缀)问题,因为每一个子串都是某个后缀的前缀

该问题用后缀数组处理出height数组可以解决

如果要求不重叠,我们对答案进行二分,对于每一次二分出来的答案,我们遍历每一个连续的height>=mid的区间,这段区间的字符串两两之间的最长公共前缀>=mid,在这段区间中找最靠前和最靠后的,计算两者的距离dis,如果存在某一个区间中的字符串dis>mid,则当前答案成立

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
const int N=20005;
int s[N],ht[N],n,m,sa[N],x[N*2],y[N*2],c[N],rk[N];
void get_sa(){
	memset(c,0,sizeof(c));
	for(int i=1;i<=n;i++)c[x[i]=s[i]]++;
	for(int i=2;i<=m;i++)c[i]+=c[i-1];
	for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
	for(int w=1;w<=n;w<<=1){
		int num=0;
		for(int i=n-w+1;i<=n;i++)y[++num]=i;
		for(int i=1;i<=n;i++)if(sa[i]>w)y[++num]=sa[i]-w;
		for(int i=1;i<=m;i++)c[i]=0;
		for(int i=1;i<=n;i++)c[x[i]]++;
		for(int i=2;i<=m;i++)c[i]+=c[i-1];
		for(int i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i];
		swap(x,y);x[sa[1]]=num=1;
		for(int i=2;i<=n;i++)
			x[sa[i]]=(y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+w]==y[sa[i]+w])?num:++num;
		if((m=num)==n)break;
	}
}
bool check(int mid){
	int mmax,mmin;
	mmax=mmin=sa[1];
	for(int i=2;i<=n;i++){
		if(ht[i]>=mid){
			mmax=max(mmax,sa[i]);
			mmin=min(mmin,sa[i]);
			continue;
		}
		if(mmax-mmin>mid)return 1;
		else mmax=mmin=sa[i];
	}
	return mmax-mmin>mid;
}
int main(){
	while(scanf("%d",&n)!=EOF&&n){
		m=300;
		for(int i=1;i<=n;i++)scanf("%d",&s[i]);n--;
		for(int i=1;i<=n;i++)s[i]=s[i+1]-s[i]+88;
		get_sa();
		memset(ht,0,sizeof(ht));
		for(int i=1;i<=n;i++)rk[sa[i]]=i;
		for(int k=0,i=1;i<=n;i++){
			if(k)k--;
			while(s[i+k]==s[sa[rk[i]-1]+k])k++;
			ht[rk[i]]=k;
		}
		int l=4,r=n,ans=-1;
		while(l<=r){
			int mid=(l+r)>>1;
			if(check(mid)){ans=mid;l=mid+1;}
			else r=mid-1;
		}
		printf("%d\n",ans+1);
	}
	return 0;
}
posted @ 2020-08-12 14:13  kakakakakaka  阅读(96)  评论(0编辑  收藏  举报

Never forget why you start

//鼠标爆炸特效