POJ 3261 浅谈后缀数组HEIGHT数组的实际应用

这里写图片描述
世界真的很大
后缀数组是个神奇东西,应用也挺多的
后缀排名这个东西很有用
这又是一道奶牛题
POJ昨天卡了害得我测了好久
看题先:
description:

给定一个长为n(1≤n≤105 )的字符串,求其最长的至少出现了k次的
可重叠子串长度。

input

第一行两个整数n,k,接下来n个整数表示序列(字符串)

output

一个整数表示答案

看到求最长什么的应该想到二分,再考虑一下是不是具有二分的性质。
如果一个串出现了k次,那么长度比其小的串肯定至少出现了k次,这是显然的
那么就可以二分了,二分一个长度,每次check看有没有这个长度的字串出现了k次及以上
考虑怎么check,后缀数组中的后缀排名,如果几个后缀有一部分公共前缀,那么在后缀排名上他们一定是挨在一起的,因为后缀排名比较的是字典序,由于这几个后缀前几个字符都是相同的,所以字典序上他们必然是挨在一起的。
我们可以利用这个性质,把后缀排名里面,挨在一起的,有公共前缀长度大于二分的值的,分为一组,这一组里满足有长度大于二分值的公共字串,再检查组内是不是有超过k个后缀,就是这个公共字串有没有出现大于等于k次
完整代码:

#include<stdio.h>
#include<algorithm>
using namespace std;

int n,k,big=0;
int wa[100010],wb[100010],wv[100010],ws[100010];
int rank[100010],height[100010];
int sa[100010],r[100010];

bool cmp(int *r,int a,int b,int l)
{
    return r[a]==r[b]&&r[a+l]==r[b+l];
}

void calheight(int *r,int *sa,int n)
{
    int k=0,j;
    for(int i=1;i<=n;i++)
        rank[sa[i]]=i;
    for(int i=0;i<n;height[rank[i++]]=k)
        for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}

void da(int *r,int *sa,int n,int m)
{
    int *x=wa,*y=wb,*t;
    for(int i=0;i<m;i++) ws[i]=0;
    for(int i=0;i<n;i++) ws[x[i]=r[i]]++;
    for(int i=1;i<m;i++) ws[i]+=ws[i-1];
    for(int i=n-1;i>=0;i--) sa[--ws[x[i]]]=i;
    for(int p=1,j=1;p<n;j<<=1,m=p)
    {
        p=0;
        for(int i=n-j;i<n;i++) y[p++]=i;
        for(int i=0;i<n;i++) if(sa[i]>=j) y[p++]=sa[i]-j;
        for(int i=0;i<n;i++) wv[i]=x[y[i]];
        for(int i=0;i<m;i++) ws[i]=0;
        for(int i=0;i<n;i++) ws[wv[i]]++;
        for(int i=1;i<m;i++) ws[i]+=ws[i-1];
        for(int i=n-1;i>=0;i--) sa[--ws[wv[i]]]=y[i];
        int i;
        for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i<n;i++)
            x[sa[i]]= cmp(y,sa[i-1],sa[i],j) ? p-1 : p++;
    }
}

bool check(int mid)
{
    int tmp=0;
    for(int i=2;i<=n;i++)
    {
        if(height[i]>=mid)
           tmp++;
        if(tmp>=k) return 1;
        if(height[i]<mid)
           tmp=1;
    }
    return false ;
}

int main()
{
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++)
        scanf("%d",&r[i]),big=max(big,r[i]);
    da(r,sa,n+1,big+1);
    calheight(r,sa,n);
    int lf=1,rg=n,ans=0;
    while(lf<=rg)
    {
        int mid=(lf+rg)>>1;
        if(check(mid))
            ans=mid,lf=mid+1;
        else
            rg=mid-1;
    }
    printf("%d\n",ans);
    return 0;
}
/*
Whoso pulleth out this sword from this stone and anvil is duly born King of all England
*/

嗯,就是这样

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值