首页 > 编程语言 > 详细

暑假 D6 T3 longest(后缀数组+二分)

时间:2019-07-15 21:40:28      阅读:84      评论:0      收藏:0      [点我收藏+]

题目描述

给定一个长为n的字符串,求其不可重叠的最长重复子串长度(即存在两个这样的子串并且不相交)

输入格式

仅一行,为题目描述的字符串

输出格式

输出一行一个整数,表示不可重叠的最长重复子串的长度

数据范围

对于100%的数据,保证1≤n≤100000,串的所有字符均为小写字母

题解

子串是后缀的前缀

所以两个后缀的lcp就是重复子串

如果有马新租条件的长度为len的重复子串,那么就有len-1的重复子串

具有二分性

将sa数组按下标列出,height[i]为sa[i]和sa[i-1]的桥梁

check函数中,将长度<mid的桥梁删去,分成了一些组,这些组内的任意两个后缀的lcp≤mid,因为两个后缀的lcp就是他们之前所有桥梁的min

那么在组内如果找到两个后缀,他们起始位置只差≥mid,那么就可以

求后缀数组时,c数组只开到了字符集大小,在luogu模板过了可还行

#include<bits/stdc++.h>
using namespace std;

const int maxn=1000005;
int n,m=26;
char s[maxn];
int x[maxn],y[maxn],c[maxn];
int sa[maxn],rk[maxn],height[maxn];

void get_sa(){
    for(int i=0;i<n;i++) c[x[i]=s[i]-a]++;
    for(int i=1;i<n;i++) c[i]+=c[i-1];
    for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
    for(int k=1;k<=n;k<<=1){
        int p=0;
        for(int i=n-k;i<n;i++) y[p++]=i;
        for(int i=0;i<n;i++) if(sa[i]>=k) y[p++]=sa[i]-k;
        for(int i=0;i<m;i++) c[i]=0;
        for(int i=0;i<n;i++) c[x[i]]++;
        for(int i=1;i<n;i++) c[i]+=c[i-1];
        for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(int i=1;i<n;i++)
         x[sa[i]]= y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k] ? p-1 : p++;
        if(p==n) break;
        m=p;
    }
}

void get_h(){
    for(int i=0;i<n;i++) rk[sa[i]]=i;
    int k=0;
    for(int i=0;i<n;i++){
        if(!rk[i]) continue;
        if(k) k--;
        int j=sa[rk[i]-1];
        while(j+k<n&&i+k<n&&s[i+k]==s[j+k]) k++;
        height[rk[i]]=k;
    }
}

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

int main(){
    freopen("longest.in","r",stdin);
    freopen("longest.out","w",stdout);
    scanf("%s",s);
    n=strlen(s);
    get_sa();
    get_h();
    int l=0,r=n,ans=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid)) ans=mid,l=mid+1;
        else r=mid-1;
    }
    printf("%d",ans);
}

 

暑假 D6 T3 longest(后缀数组+二分)

原文:https://www.cnblogs.com/sto324/p/11191625.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!