【BZOJ4310】跳蚤,后缀数组+ST表求LCP+二分答案

Time:2016.05.26
Author:xiaoyimi
转载注明出处谢谢


传送门
思路:
首先要求出不同子串的个数
有这样一个性质

一个串中不同子串的总数=∑(len-height[i]-sa[i])

然后就可以通过二分子串的排名,来判断当前排名的子串是否符合要求
比较任意两子串的排名用RMQ求LCP
还有一个问题就是知道排名,求其串。
这个要从后向前枚举,一个个减去就好
注意:
注意下标的处理和后缀数组自身的性质
代码:

#include<bits/stdc++.h>
#define M 100003
#define LL long long
using namespace std;
int len,k,ans_l,ans_r,ls,rs;LL sum;
int cnt[M],w[M],sa[M],rank[M],tmp[M],id[M],height[M],fa[M][18];
char s[M];
void SA(int len,int up)
{
    int d=1,p=0,*t=tmp,*rk=rank;
    for (int i=0;i<len;i++) cnt[rk[i]=w[i]]++;
    for (int i=1;i<up;i++) cnt[i]+=cnt[i-1];
    for (int i=len-1;i>=0;i--) sa[--cnt[rk[i]]]=i;
    for (;;)
    {
        for (int i=len-d;i<len;i++) id[p++]=i;
        for (int i=0;i<len;i++)
            if (sa[i]>=d) id[p++]=sa[i]-d;
        for (int i=0;i<up;i++) cnt[i]=0;
        for (int i=0;i<len;i++) cnt[t[i]=rk[id[i]]]++;
        for (int i=1;i<up;i++) cnt[i]+=cnt[i-1];
        for (int i=len-1;i>=0;i--) sa[--cnt[t[i]]]=id[i];
        p=1;
        swap(t,rk);
        rk[sa[0]]=0;
        for (int i=0;i<len-1;i++)
            if (sa[i]+d<len&&sa[i+1]+d<len&&t[sa[i]]==t[sa[i+1]]&&t[sa[i]+d]==t[sa[i+1]+d])
                rk[sa[i+1]]=p-1;
            else
                rk[sa[i+1]]=p++;
        if (p==len) break;
        d<<=1;up=p;p=0;
    }
}
void Height(int len)
{
    for (int i=1;i<=len;i++) rank[sa[i]]=i;
    int k=0,x;
    for (int i=0;i<len;i++)
    {
        x=sa[rank[i]-1];
        k=max(k-1,0);
        while (w[x+k]==w[i+k]) k++;
        height[rank[i]]=k;
    }
}
void Kth(LL x)
{
    LL t=x;
    for (int i=1;i<=len;i++)
        if (t>len-sa[i]-height[i]) t-=len-sa[i]-height[i];
        else {ls=sa[i];rs=sa[i]+height[i]+t-1;return;} 
}
int LCP(int a,int b)
{
    if (a==b) return len-sa[a];
    if (a>b) swap(a,b);
    int k=log2(b-a); 
    return min(fa[a+1][k],fa[b-(1<<k)+1][k]);
}
bool compare(int x1,int y1,int x2,int y2)
{
    int l1=y1-x1+1,l2=y2-x2+1,lcp=LCP(rank[x1],rank[x2]);
    if (lcp>=l1) return l1<=l2;
    else if (lcp>=l2) return 0;
    else return s[x1+lcp]<=s[x2+lcp];
}
bool check()
{
    int last=len-1,p=1;
    for (int i=len-1;i>=0;i--)
        if (s[i]>s[ls]) return 0;
        else if (!compare(i,last,ls,rs))
        {
            p++;last=i;
            if (p>k) return 0;
        }
    return 1;
}
main()
{
    scanf("%d",&k);
    scanf("%s",s);
    len=strlen(s);
    for (int i=0;i<len;i++) w[i]=s[i]-'a'+1;
    SA(len+1,28);
    Height(len);
    for (int i=1;i<=len;i++) sum+=len-sa[i]-height[i];
    for (int i=1;i<=len;i++) fa[i][0]=height[i];
    for (int j=1;j<=17;j++)
        for (int i=1;i<=len-(1<<j-1);i++)
            fa[i][j]=min(fa[i][j-1],fa[i+(1<<j-1)][j-1]);
    LL l=1,r=sum,mid;
    while (l<=r)
    {
        mid=l+r>>1;
        Kth(mid);
        if (check()) ans_l=ls,ans_r=rs,r=mid-1;
        else l=mid+1;
    } 
    for (int i=ans_l;i<=ans_r;i++) putchar(s[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值