POJ 1743 Musical Theme(后缀数组+二分)

81 篇文章 0 订阅
22 篇文章 0 订阅

Description
给出一个数字串,求这个数字串中长度最少为5的最长重复子串的长度(重复字串不需要完全相同但不能有重叠,只要某个字串同时加减一个相同的值后变为另一个字串即可)
Input
多组用例,每组用例首先输入一整数n(n<=20000)表示数字串长度,之后输入n个整数(介于0~88之间)表示该数字串,以n=0结束输入
Output
输出该数字串中长度至少为5的最长重复子串的长度,如果不存在则输出0
Sample Input
30
25 27 30 34 39 45 52 60 69 79 69 60 52 45 39 34 30 26 22 18
82 78 74 70 66 67 64 60 65 80
0
Sample Output
5
Solution
后缀数组,将n个数变成n-1个差值(即a[i]=a[i+1]-a[i]+100,加100是为避免负数出现),那么问题变为真正的重复子串问题,二分最大长度,对于每个二分值k,将height数组按k分组,找出每个组的sa最大值Max和最小值Min,如果Max-Min>=k说明这两个子串不重叠,满足条件,如果所有组都不满足则不满足条件,注意这样算出的最长重复子串长度是差值串的,原串的最长重复字串长度还需要在这个基础上加一
Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
//后缀数组 
#define maxn 44444
int t1[maxn],t2[maxn],c[maxn],sa[maxn],rank[maxn],height[maxn];
bool cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int str[],int n,int m)
{
    n++;
    int i,j,p,*x=t1,*y=t2;
    for(i=0;i<m;i++)c[i]=0;
    for(i=0;i<n;i++)c[x[i]=str[i]]++;
    for(i=1;i<m;i++)c[i]+=c[i-1];
    for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
    for(j=1;j<=n;j<<=1)
    {
        p=0;
        for(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<m;i++)c[i]=0;
        for(i=0;i<n;i++)c[x[y[i]]]++;
        for(i=1;i<m;i++)c[i]+=c[i-1];
        for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;x[sa[0]]=0;
        for(i=1;i<n;i++)
        x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
        if(p>=n)break;
        m=p;
    }
    int k=0;
    n--;
    for(i=0;i<=n;i++)rank[sa[i]]=i;
        for(i=0;i<n;i++)
        {
            if(k)k--;
            j=sa[rank[i]-1];
            while(str[i+k]==str[j+k])k++;
            height[rank[i]]=k;
        }
}
int n,a[maxn];
int check(int k)
{
    int Min,Max;
    Min=Max=sa[1];
    for(int i=2;i<=n;i++)
    {
        if(height[i]>=k&&i<n)
        {
            Min=min(Min,sa[i]);
            Max=max(Max,sa[i]);
            continue;
        }
        if(Max-Min>=k)return 1;
        Min=Max=sa[i];
    }
    return 0;
}
int main()
{
    while(~scanf("%d",&n),n)
    {
        for(int i=0;i<n;i++)scanf("%d",&a[i]);
        for(int i=0;i<n-1;i++)a[i]=a[i+1]-a[i]+100;
        a[--n]=0;
        da(a,n,200);
        int l=4,r=n,ans=0;
        while(l<=r) 
        {
            int mid=(l+r)>>1;
            if(check(mid))
            {
                ans=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        ans++;
        printf("%d\n",ans>=5?ans:0);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值