POJ 1743 Musical Theme[后缀数组]

来源:互联网 发布:苹果电脑设计软件下载 编辑:程序博客网 时间:2024/06/11 22:31
题目链接:http://poj.org/problem?id=1743
题目大意
给定一段音乐乐谱,其中的音符用数字表示出来( 范围 [ 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){
    returna<b?a:b;
}
int max(int a,int b){
    returna>b?a:b;
}
int wa[N],wb[N],wv[N],Ws[N];
int cmp(int *r,int a,int b,int l)
{returnr[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){
    intmmax=0,mmin=n;
    for(inti=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;
           }
       }
    }
    returnfalse;
}
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 0
原创粉丝点击