POJ 1743 Musical Theme(后缀数组学习+该题解法)

题意:有N个音符组成的一首乐曲,每个音符是一个1~88的整数,现在要找一个最长的主题,即是该音符序列的一个子串,主题还要满足以下三个条件:

1.最少由五个音符组成
2.该主题在乐曲中出现最少两次(两个主题不一定要一模一样,只要一个可以由另个一转置得到即视为同一种主题,转置:主题序列中的每一个字符都被加上或者减去了同一个数字)
3.每两个主题不可有重叠的部分
如果没有装置这个要求的话,很显然是求不可重叠最长重复子串,用后缀数组就可以做,但是我们观察转置的定义,经过一些处理可以将题目化为不可重复最长重复子串。假设一个子串是a,b,c,d,e.另一个子串是a+k,b+k,c+k,d+k,e+k.显然他们是同一个主题,我们发现将他们对每相邻两个字符求差值得到的差值串是一样的。
a,b,c,d,e -> b-a,c-b,d-c,e-d
a+k,b+k,c+k,d+k -> b-a,c-b,d-c,e-d
于是可以将原序列每相邻连个求差值得到新序列,然后求其后缀数组。但是由于化成新序列之后字符数量减少1.我们找的主题变成了最少由四个字符组成就可以,但是这里面有个小问题,比如
1 1 1 1 1 1 1 1 1 ----->0 0 0 0 0 0 0 0这个显然是不满足要求的,因此要求两个主题之间至少要相差一个字符,这样就可以解决这个问题了

"不可重叠最长重复子串"解法(摘自罗穗骞《后缀数组——处理字符串的有力工具》):

先二分答案,把题目变成判定性问题:判断是否存在两个长度为k的子串是相同的,且不重叠。解决这个问题的关键还是利用height数组(height在代码中有解释)。把排序后的后缀分成若干组,其中每组的后缀之间的height值都不小于k。例如,字符串为“aabaaaab”,当k=2时,后缀分成了4组,如图所示

容易看出,有希望成为最长公共前缀不小于k的两个后缀一定在同一组。然后对于每组后缀,只须判断每个后缀的sa值的最大值和最小值之差是否不小于k。如果有一组满足,则说明存在,否则不存在。整个做法的时间复杂度为O(nlogn)

#pragma GCC optimize(2)
//#include<bits/stdc++.h>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;

typedef long long ll;
typedef unsigned long ul;
typedef unsigned long long ull;
#define pi acos(-1.0)
#define e exp(1.0)
#define pb push_back
#define mk make_pair
#define fir first
#define sec second
//#define scf scanf
//#define prf printf
typedef pair<ll,ll> pa;
const ll INF=0x3f3f3f3f3f3f3f3f;
const ll maxn=2e5+7;
ll height[maxn],sa[maxn],rank[maxn],tmp[maxn],r[maxn],K,N;
bool cmp(ll i,ll j){//i,j是后缀开始的位置 
	if(rank[i]!=rank[j])
	return rank[i]<rank[j];
	ll r1=i+K<=N?rank[i+K]:-1;//i+K==N 相当于空串 
	ll r2=j+K<=N?rank[j+K]:-1;
	return r1<r2;
}
//倍增法求后缀数组 
void do_sa(){
	ll i,j;
	//给rank[]和sa[]数组赋值, 
	for(i=0;i<=N;i++){
		sa[i]=i;
//		rank[i]=(i==N?-1:r[i]);//包含空串 
		rank[i]=r[i];
	}
	//倍增法求后缀树组 
	for(K=1;K<=N;K<<=1){
		sort(sa,sa+1+N,cmp);
		tmp[sa[0]]=0;//最小一定是空串 
		for(i=1;i<=N;i++)
		tmp[sa[i]]=tmp[sa[i-1]]+(cmp(sa[i-1],sa[i])?1:0);
		//sa[i]和sa[i-1]的顺序千万别弄反了,和上面的cmp函数的定义是对应的,如果写反,当r1==r2是不等价的 
		for(i=0;i<=N;i++)
		rank[i]=tmp[i];
	}
	return ;
}
//height[i]数组存储从i处开始的后缀字符串和排名小于当相邻的位置j处开始的后缀
//字符串子串的最长公共子串长度 
void get_height(){
	ll i,j,k=0;
	for(i=0;i<N;i++){//
		if(k)
		k--;
		else
		k=0;
		j=sa[rank[i]-1];
		while(r[i+k]==r[j+k])
		k++;
		height[rank[i]]=k;
	}
	return ;
}
bool check(ll mid){
	ll i,j;
	ll maxx=-INF,minn=INF;
	for(i=1;i<=N;i++){
		if(height[i]>=mid){
			minn=min(minn,min(sa[i],sa[i-1]));
            maxx=max(maxx,max(sa[i],sa[i-1]));
            if(maxx-minn>mid) return true;
		}
		else{
			maxx=-INF;
			minn=INF;
		}
	}
	return false;
}
int main()
{
//  freopen(".../.txt","w",stdout);
//  freopen(".../.txt","r",stdin);
//	ios::sync_with_stdio(false);
	while(scanf("%lld",&N)&&N){
		ll i,j,k;
//		memset(height,0,sizeof(height));
//		memset(rank,0,sizeof(rank));
//		memset(tmp,0,sizeof(tmp));		
//		memset(sa,0,sizeof(sa));
//		memset(r,0,sizeof(r));
		//求差值串,r[]相当于字符串 
		for(i=0;i<N;i++){
			scanf("%lld",&r[i]);
			if(i)
			r[i-1]=r[i]-r[i-1]+88;
		}
		N--;
		r[N]=0;
		do_sa();
		get_height();
		ll L=0,R=N/2,mid,res=0;
		while(L<=R){
			mid=(R-L)/2+L;
			if(check(mid)){
				L=mid+1;
				res=max(res,mid); 
			}
			else{
				R=mid-1;
			}
		}
		if(res<4){
			printf("0\n");
			continue;
		}
		printf("%lld\n",res+1);	
	}
	return 0;
}

上面使用的后缀数组模板使用的《挑战程序设计竞赛》,书上对后缀数组的讲解的截图如下

、
由于在调用cmp函数时两个参数的额传入顺序写反了,导致对后缀子串排序错误,害我郁闷了很久……

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值