【POJ 1743】Musical Theme

传送门


Problem

有一个长度为 n n n 的序列,我们称两个子序列是等价的,当且仅当其中一个子序列中所有的元素加 / / /减同一个数后与另一个子序列相同,如 1 , 2 , 4 1,2,4 1,2,4 5 , 6 , 8 5,6,8 5,6,8 等价。

我们称一个子序列是 m u s i c music music,当且仅当它满足一下条件:

  1. 它的长度至少是 5 5 5
  2. 存在另一个字符串与它等价。
  3. 它和与它等价的字符串不能相交。

求出满足条件的子序列的最大长度,如无解输出 0 0 0

数据规模: 1 ≤ n ≤ 20000 1\le n\le20000 1n20000 a i ∈ [   1 , 88   ] a_i\in[\,1,88\,] ai[1,88]


Solution

题目大意是我自己 y y yy yy 出来的。(小声 b b bb bb

我们注意到,对于两个等价的子序列,它们具体的值可能不同,但是元素间的相对大小是相同的。于是我们令 b i = a i − a i − 1 b_i=a_i-a_{i-1} bi=aiai1,等价的子序列的 b i b_i bi 值肯定是一一对应相等的。

那么对于 b i b_i bi ,就是不可重叠最长重复子串模板,直接用后缀数组就可以了。


Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 20005
using namespace std;
int n,m,x[N],y[N],S[N],height[N],Rank[N],sa[N],sum[N];
void solve(){
	int i,j;
	for(i=1;i<=m;++i)  sum[i]=0;
	for(i=1;i<=n;++i)  sum[x[i]=S[i]]++;
	for(i=2;i<=m;++i)  sum[i]+=sum[i-1];
	for(i=n;i>=1;--i)  sa[sum[x[i]]--]=i;
	for(j=1;j<=n;j<<=1){
		int num=0;
		for(i=n-j+1;i<=n;++i)  y[++num]=i;
		for(i=1;i<=n;++i)
			if(sa[i]>j)  y[++num]=sa[i]-j;
		for(i=1;i<=m;++i)  sum[i]=0;
		for(i=1;i<=n;++i)  sum[x[i]]++;
		for(i=2;i<=m;++i)  sum[i]+=sum[i-1];
		for(i=n;i>=1;--i)  sa[sum[x[y[i]]]--]=y[i];
		swap(x,y);
		num=1,x[sa[1]]=1;
		for(i=2;i<=n;++i)
			x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+j]==y[sa[i-1]+j])?num:++num;
		if(n==num)  break;
		m=num;
	}
}
void GetH(){
	int i,k=0;
	for(i=1;i<=n;++i)  Rank[sa[i]]=i;
	for(i=1;i<=n;++i){
		if(Rank[i]==1)  continue;
		int j=sa[Rank[i]-1];
		while(S[i+k]==S[j+k])  k++;
		height[Rank[i]]=k;
		if(k)  k--;
	}
}
bool check(int mid){
	int Min,Max;
	Min=Max=sa[1];
	for(int i=2;i<=n;++i){
		if(height[i]<mid)
			Min=Max=sa[i];
		else{
			Min=min(Min,sa[i]);
			Max=max(Max,sa[i]);
			if(Max-Min>mid)  return true;
		}
	}
	return false;
}
int main(){
	int i;
	while(~scanf("%d",&n)){
		if(!n)  break;
		for(i=1;i<=n;++i)  scanf("%d",&S[i]);
		for(i=1;i<n;++i)  S[i]=S[i+1]-S[i]+100;
		m=200,solve(),GetH();
		int l=1,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?0:l+1);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值