poj1743-后缀数组(不重叠最长重复子串)

给你一串数字,求它们最长的重复(公差相同)子序列,且两个子序列不相交
大致思路:后缀数组+二分答案

问题:
1、将公差相同的子串转化为重复子串问题
比如说原序列为:a1 a2 a3 a4 a5 a6 a7
如果子串a3a4,a6a7是公差相同的子串,则a7-a4==a6-a3
移项:a7-a6=a4-a3 相邻的差相同?
于是想到原序列的差分序列:
d1 d2 d3 d4 d5 d6 d7(d1=a1-a0,d2=a2-a1……)
若 d2,d3和d6,d7为重复子串,则
d6=d2,d7=d3
a6-a5=a2-a1
a7-a6=a3-a2,移项:
a6-a2=a5-a1
a7-a3=a6-a2,则
a1a2a3和a5a6a7是原串的公差相同的子串(长度+1)
这样就转化成了求差分序列的后缀数组问题

2、二分答案,判断当前长度是否可行

O(logn)二分枚举子串长度,判断解是否成立
O(n)判断长度是否成立:把互相之间LCP大于等于长度的分为一组,这通过个扫一遍height即可,因为后缀是有序的,相邻的后缀间的LCP必定的极大的(若相隔多个则取最小值);接下来就找到每个组里后缀sa值最大和最小的,如果差值大于k就成立,因为这样小下标的后缀沿着LCP下去走k步才不会盖到大下标的后缀。

下面附上代码:

#include "iostream"
#include "cstdio"
#include "cstring"
#include "string"
#include "algorithm"
#define maxn 20005
using namespace std;
int s[maxn],a[maxn];
int sa[maxn],se[maxn],rk[maxn],x[maxn],height[maxn],tank[maxn],n,m;
void build(){
    m=400;
    for(int i=0;i<=m;i++) tank[i]=0;
    for(int i=1;i<=n;i++) tank[x[i]=a[i]]++;
    for(int i=1;i<=m;i++) tank[i]+=tank[i-1];
    for(int i=n;i>=1;i--) sa[tank[a[i]]--]=i;
    for(int j=1;j<=n;j<<=1){
        int p=0;
        for(int i=n-j+1;i<=n;i++) se[++p]=i;
        for(int i=1;i<=n;i++) if(sa[i]>j) se[++p]=sa[i]-j;//第二关键字已经有序;
        for(int i=0;i<=m;i++) tank[i]=0;
        for(int i=1;i<=n;i++) tank[x[se[i]]]++;
        for(int i=1;i<=m;i++) tank[i]+=tank[i-1];
        for(int i=n;i>=1;i--) sa[tank[x[se[i]]]--]=se[i];
        swap(se,x);
        x[sa[1]]=1;p=1;
        for(int i=2;i<=n;i++){
            x[sa[i]]=se[sa[i]]==se[sa[i-1]]&&se[sa[i]+j]==se[sa[i-1]+j]?p:++p;
        }
        if(p>=n) break;
        m=p;
    }
}
void get_height(){
    for(int i=1;i<=n;i++) rk[sa[i]]=i;
    int k=0;
    for(int i=1;i<=n;i++){
        if(rk[i]==1) continue;
        if(k) k--;
        int j=sa[rk[i]-1];
        while(i+k<=n&&j+k<=n&&a[i+k]==a[j+k]) ++k;
        height[rk[i]]=k;
    }
}
int judge(int len){//二分长度
    int mx=0,mn=0x3f3f3f3f;
    for(int i=2;i<=n;i++){
        if(height[i]>=len){
            mx=max(mx,max(sa[i],sa[i-1]));
            mn=min(mn,min(sa[i],sa[i-1]));
            if(mx-mn>len) return 1;
        }
        else{
            mx=0,mn=0x3f3f3f3f;
        }
    }
    return 0;
}
int main(){
    while(scanf("%d",&n)&&n){
        s[0]=-200;
        for(int i=1;i<=n;i++) scanf("%d",&s[i]);
        for(int i=1;i<=n;i++){
            a[i]=s[i]-s[i-1];
            a[i]+=90;
        }//做差很妙!!!
        build();
        get_height();
        int l=0,r=n;
        while(l<r){
            int mid=l+r+1>>1;
            if(judge(mid)) l=mid;
            else r=mid-1;
        }
        if(l+1>=5) printf("%d\n",l+1);
        else printf("%d\n",0);
    }
    return 0;
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值