0
点赞
收藏
分享

微信扫一扫

POJ 1743 Musical Theme[后缀数组]


题目大意



给定一段音乐乐谱,其中的音符用数字表示出来( 范围 [ 1,88 ] ),要求的是这段乐谱的主旋律。所谓的主旋律,就是满足以下三点要求的一段子串:



  1、音符的数目至少为5;



  2、重复出现在乐谱中的另一个地方;



  3、不相互叠加;



先二分答案,把题目变成判定性问题:判断是否存在两个长度为k 的子串是相同的,且不重叠。



解决这个问题的关键是利用height 数组,把排序后的后缀分成若干组,



其中每组的后缀之间的height值都不小于k。



容易看出,有希望成为最长公共前缀不小于k 的两个后缀一定在同一组。



然后对于每组后缀,只须判断每个后缀的sa 值的最大值和最小值之差是否不小于k。



如果有一组满足,则说明存在,否则不存在。时间复杂度为O(nlogn)。



代码:



#include <iostream>

#include <cstdio>

#include <cstring>

#include<cmath>

#include <algorithm>

const int N=200010;

int min(int a,int b){




return a<b?a:b;

}

int max(int a,int b){




return a>b?a:b;

}

int wa[N],wb[N],wv[N],Ws[N];

int cmp(int *r,int a,int b,int l)

{return r[a]==r[b]&&r[a+l]==r[b+l];}

void da(const int *r,int *sa,int n,int m){


int i,j,p,*x=wa,*y=wb,*t;


for(i=0;i<m;i++) Ws[i]=0;


for(i=0;i<n;i++) Ws[x[i]=r[i]]++;


for(i=1;i<m;i++) Ws[i]+=Ws[i-1];


for(i=n-1;i>=0;i--) sa[--Ws[x[i]]]=i;


for(j=1,p=1;p<n;j*=2,m=p){



for(p=0,i=n-j;i<n;i++) y[p++]=i;



for(i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;



for(i=0;i<n;i++) wv[i]=x[y[i]];



for(i=0;i<m;i++) Ws[i]=0;



for(i=0;i<n;i++) Ws[wv[i]]++;



for(i=1;i<m;i++) Ws[i]+=Ws[i-1];



for(i=n-1;i>=0;i--) sa[--Ws[wv[i]]]=y[i];



for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)




x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;


}


return;

}

int sa[N],Rank[N],height[N];

//求height数组

void calheight(const int *r,int *sa,int n){


int i,j,k=0;


for(i=1;i<=n;i++) Rank[sa[i]]=i;


for(i=0;i<n;height[Rank[i++]]=k)



for(k?k--:0,j=sa[Rank[i]-1];r[i+k]==r[j+k];k++);


return;

}

int n,num[N],a[N];

bool check(int mid){




int mmax=0,mmin=n;




for(int i=0;i<=n;i++){








if(height[i]<mid){












mmax=sa[i];












mmin=sa[i];








}








else{












mmax=max(mmax,max(sa[i],sa[i-1]));












mmin=min(mmin,min(sa[i],sa[i-1]));












if(mmax-mmin>mid){
















return true;












}








}




}




return false;

}

int main()

{



//freopen("C:\\Users\\acm\\Desktop\\001.in","r",stdin);




int i;




while(scanf("%d",&n)&&n){








for(i=0;i<n;i++){












scanf("%d",&a[i]);








}








n--;








for(i=0;i<n;i++){












num[i]=a[i+1]-a[i]+100;








}








num[n]=0;








da(num,sa,n+1,200);








calheight(num,sa,n);








if(!check(4)){




printf("0\n");




continue;



}



int ans,low=0,high=n,mid;



while(low<=high){




mid=(low+high)/2;




if(check(mid)){





ans=mid;





low=mid+1;




}




else





high=mid-1;



}



printf("%d\n",ans+1);


}


return 0;

}

得到答案的值的时候,并不是音符的长度,而是区间的长度,所以ans要加1。

举报

相关推荐

0 条评论