[后缀数组]poj1743 Musical Theme

poj1743——Musical Theme

一句话题意就是:

给定一个字符串,求最长重复子串,这两个子串不能重叠。

首先因为N太大了(1<=N<=20000),所以我们可以考虑用后缀数组

算法分析:

先二分答案,把题目变成判定性问题:1、判断是否存在两个长度为k的子串是相同的;2、不重叠。先将后缀进行进行基数排序,把排序后的后缀分成若干组,其中每组的后缀之间的height值都不小于k。例如,字符串为“aabaaaab”,当k=2时,后缀分成了4组,如图所示

这里写图片描述

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

代码

#include<cmath>

#include<cstdio>

#include<cstdlib>

#include<cstring>

#include<iostream>

#include<algorithm>

using namespace std;

inta[210000],wr[210000],mc[210000],JS[210000],sa[210000],y[210000],height[210000];

bool cmp(int k1,int k2,int ln){

   return wr[k1]==wr[k2]&&wr[k1+ln]==wr[k2+ln];

}

void get_sa(int n,int m){//构建SA后缀数组(即:排第几的是谁)

    inti,k,p,ln;

   //memcpy(mc,a,sizeof(a));

   for(i=1;i<=n;i++)mc[i]=a[i];

    //a数组:原字符串,mc名次数组(即:你排第几)

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

   for(i=1;i<=n;i++)JS[mc[i]]++;

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

   for(i=n;i>=1;i--)sa[JS[mc[i]]--]=i; 

    //以上四句为基数排序

   ln=1;p=0;

   //ln为当前子串的长度,p表示有多少不相同的子串

   while(p<n)

   //如果p等于n,那么函数可以结束。因为在当前长度的字符串中,已经没有相同的字符串,接下来的排序不会改变rank值。

{

       for(k=0,i=n-ln+1;i<=n;i++)y[++k]=i;

       for(i=1;i<=n;i++)if(sa[i]-ln>0)y[++k]=sa[i]-ln;

       for(i=1;i<=n;i++)wr[i]=mc[y[i]];

       //数组y保存的是对第二关键字排序的结果。

       //数组wr保存的是对第二关键字排序后的mc值

       //以下为对第一关键字排序

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

       for(i=1;i<=n;i++)JS[wr[i]]++;

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

       for(i=n;i>=1;i--)sa[JS[wr[i]]--]=y[i];

       memcpy(wr,mc,sizeof(wr));  

       p=1;mc[sa[1]]=1;

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

           if(!cmp(sa[i],sa[i-1],ln))p++;

           mc[sa[i]]=p;

       }

       //得到新的mc数组。这里要注意的是,可能有多个字符串的rank值是相同的,所以必须比较两个字符串是否完全相同

       m=p;ln*=2;

    }

   a[0]=0;sa[0]=0;

}

void get_he(int n){

    inti,j,k=0;

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

       j=sa[mc[i]-1];

       if(k)k--;

       while(a[j+k]==a[i+k])k++;

       height[mc[i]]=k;

    }

}

bool check(int n,int k){

    intmaxx=sa[1],minn=sa[1];

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

       if(height[i]<k)maxx=minn=sa[i];

       else{

           minn=min(minn,sa[i]);

           maxx=max(maxx,sa[i]);

           if(maxx-minn>k)return true;

       }

    }

   return false;

}

void erfen(int n) //二分

{

    intl,r,mid,ans;

   l=1;r=n;

   while(l<=r)

       {

        mid=(l+r)/2;

       if(check(n,mid))

              {

           ans=mid;

           l=mid+1;

       }   

       else r=mid-1;

    }

   if(ans>=4)printf("%d\n",ans+1); //题目要求要4个音符相同才可算为音乐元素

   else printf("0\n");

}

int main()

{

    intn,k,p;

   while (scanf("%d",&n)!=EOF)

       {

        if(n==0)break;

       memset(mc,0,sizeof(mc));

       memset(a,0,sizeof(a));

       memset(sa,0,sizeof(sa));

       scanf("%d",&p);

       for(int i=1;i<n;i++)

              {

           scanf("%d",&k);

           a[i]=k-p+100;

           p=k;

       }

       get_sa(n-1,200);

       get_he(n-1);

       erfen(n-1);

    }

   return 0;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值