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

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;
}

你可能感兴趣的:(POJ 1743 Musical Theme(后缀数组+二分))