poj1743 Musical Theme【后缀数组+二分答案】

题目大意:

给n个数组成的串,求是否有多个“相似”且间隔至少为1的子串的长度大于等于5,两个子串相似当且仅当长度相等且每一位的数字差都相等。

解题思路:

首先把问题转化成重复子串的问题:把原串每一位都与前一位相减。这样得出的新串如果有两个长度为n的子串相同,那么它们对应在原串的长度n+1的子串也就相似。

再求出后缀数组。

如果只是求可重叠重复最长子串,答案就是height的最大值。

但这道题要求重复子串间隔至少为1。

先二分答案,转化成判别式的问题比较好处理。假设当前需要判别长度为k是否符合要求,只需把排序后的后缀分成若干组,其中每组的后缀之间的height 值都不小于k,再判断其中有没有不重复的后缀,具体就是看最大的SA值和最小的SA值相差超不超过k,有一组大于k就是合法答案。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

const int N=20005;
int n,m,a[N],rank[N],sa[N],tp[N],c[N],height[N];

void Rsort()
{
    for(int i=1;i<=m;i++)c[i]=0;
    for(int i=1;i<=n;i++)c[rank[tp[i]]]++;
    for(int i=1;i<=m;i++)c[i]+=c[i-1];
    for(int i=n;i;i--)sa[c[rank[tp[i]]]--]=tp[i];
}

void SA_init()
{
    m=200;
    for(int i=1;i<=n;i++)rank[i]=a[i],tp[i]=i;
    Rsort();
    for(int w=1;w<n;w<<=1)
    {
        int j=0;
        for(int i=n-w+1;i<=n;i++)tp[++j]=i;
        for(int i=1;i<=n;i++)if(sa[i]>w)tp[++j]=sa[i]-w;
        Rsort();
        for(int i=1;i<=n;i++)swap(rank[i],tp[i]);
        rank[sa[1]]=j=1;
        for(int i=2;i<=n;i++)rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]&&tp[sa[i]+w]==tp[sa[i-1]+w]?j:++j);
        m=j;
    }
    int w=0,j;
    for(int i=1;i<=n;height[rank[i++]]=w)
        for(w=w?w-1:w,j=sa[rank[i]-1];a[i+w]==a[j+w];w++);
}

bool check(int k)
{
    int mi=n+1,mx=0;
    for(int i=2;i<=n;i++)
    {
        if(height[i]>=k)
        {
            mi=min(mi,min(sa[i],sa[i-1]));
            mx=max(mx,max(sa[i],sa[i-1]));
            if(mx-mi>k)return true;
        }
        else mi=n+1,mx=0;
    }
    return false;
}

int main()
{
    //freopen("lx.in","r",stdin);
    while(n=getint())
    {
        for(int i=1;i<=n;i++)a[i]=getint();
        n--;
        for(int i=1;i<=n;i++)a[i]=a[i+1]-a[i]+100;
        SA_init();
        int l=0,r=n;
        while(l<=r)
        {
            int mid=l+r>>1;
            if(check(mid))l=mid+1;
            else r=mid-1;
        }
        if(r>=4)printf("%d\n",r+1);
        else puts("0");
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值