BZOJ3998: [TJOI2015]弦论 解题报告

15 篇文章 0 订阅
4 篇文章 0 订阅

Description

对于一个给定长度为N的字符串,求它的第K小子串是什么。

Input

第一行是一个仅由小写英文字母构成的字符串S

第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个。T=1则表示不同位置的相同子串算作多个。K的意义如题所述。

Output

输出仅一行,为一个数字串,为第K小的子串。如果子串数目不足K个,则输出-1



题解:

这里介绍SA的做法(相对SAM会慢一些)
先处理出有多少个不相同的子串,对于T=0的询问,直接找第k小子串即可
对于T=1的询问,二分答案是第几小的串,找有多少个比他小的串(位置不同算作多个)
至于怎么找有多少个串比某个串小,假设当前的串在SA上排 i ,长度为len,那么排名在他之前的 i1 个串的都比他小,排名在他之后的串,看两个串的最长公共前缀长度即可


出现的错误
str赋值时-‘a’不+1在求height值时有边界问题
没有用longlong


(我的代码T了,优化了一天的常数还是T,但是要了数据本地评测是能过的)
这里写图片描述

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<vector>
#include<string>
#include<bitset>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define inf 1e9
using namespace std;

const int maxn = 510000;
int t[maxn],fir[maxn],sec[maxn];
int sa[maxn],rank[maxn],height[maxn];
ll sl[maxn],sh[maxn];
void sort_(int *ret,int *rk,int *str,int n,int m)
{
    for(int i=0;i<=m;i++)t[i]=0;
    for(int i=1;i<=n;i++)t[str[rk[i]]]++;
    for(int i=1;i<=m;i++)t[i]+=t[i-1];
    for(int i=n;i>=1;i--)ret[t[str[rk[i]]]--]=rk[i];
}
int str[maxn];
char st[maxn];
void get_sa(int n,int m)
{
    for(int i=1;i<=n;i++) rank[i]=i;
    //sort_(sa,rank,str,n,m);
    for(int i=0;i<=m;i++)t[i]=0;
    for(int i=1;i<=n;i++)t[str[rank[i]]]++;
    for(int i=1;i<=m;i++)t[i]+=t[i-1];
    for(int i=n;i>=1;i--)sa[t[str[rank[i]]]--]=rank[i];

    rank[sa[1]]=1;
    for(int i=2;i<=n;i++)
    rank[sa[i]]=rank[sa[i-1]]+(str[sa[i]]!=str[sa[i-1]]);
    int k=rank[sa[n]],pw=1;
    while(k!=n)
    {
        for(int i=1;i<=n;i++)
        {
            sa[i]=i;
            fir[i]=rank[i];
            sec[i]=i+pw>n?0:rank[i+pw];
        }
        //sort_(rank,sa,sec,n,n);
        //sort_(sa,rank,fir,n,n);
        for(int i=0;i<=n;i++)t[i]=0;
        for(int i=1;i<=n;i++)t[sec[sa[i]]]++;
        for(int i=1;i<=n;i++)t[i]+=t[i-1];
        for(int i=n;i>=1;i--)rank[t[sec[sa[i]]]--]=sa[i];

        for(int i=0;i<=n;i++)t[i]=0;
        for(int i=1;i<=n;i++)t[fir[rank[i]]]++;
        for(int i=1;i<=n;i++)t[i]+=t[i-1];
        for(int i=n;i>=1;i--)sa[t[fir[rank[i]]]--]=rank[i];

        rank[sa[1]]=1;
        for(int i=2;i<=n;i++)
            rank[sa[i]]=rank[sa[i-1]]+
            (fir[sa[i]]!=fir[sa[i-1]]||sec[sa[i]]!=sec[sa[i-1]]);
        k=rank[sa[n]];pw<<=1;
    }
}
void get_height(int n)
{
    height[1]=0;int k=0;
    for(int i=1;i<=n;i++)
    {
        if(k)k--;
        if(rank[i]==1)continue;
        while(str[i+k]==str[sa[rank[i]-1]+k])k++;
        height[rank[i]]=k;
    }
}
int n,m,qx,qy;
ll num;
void find_(int k,int &id,int &len)
{
    int l=1,r=n;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(k<=sh[mid])r=mid-1;
        else l=mid+1;
    }
    len=k-sh[r]+height[r+1];
    id=r+1;
}
bool judge(int k)
{
    int tx, ty;
    find_(k,tx,ty);
    ll ret=ty+sl[tx-1];
    int hi=ty;
    for(int i=tx+1;i<=n;i++) 
    {
        if(height[i]<hi)hi=height[i];
        if(hi==0) break;
        ret+=hi;
    }
    if(ret>=qy)return true;
    else return false;
}
int C()
{
    int l=1,r=qy;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(judge(mid))r=mid-1;
        else l=mid+1;
    }
    return r+1;
}

int main()
{
    //freopen("string.in","r",stdin);
    //freopen("string.out","w",stdout);

    scanf("%s",st); n=strlen(st);
    scanf("%d%d",&qx,&qy);
    for(int i=1;i<=n;i++)str[i]=st[i-1]-'a'+1;
    get_sa(n,28);
    get_height(n);
    for(int i=1;i<=n;i++)sl[i]=sl[i-1]+n-sa[i]+1;
    for(int i=1;i<=n;i++)sh[i]=sh[i-1]+n-sa[i]+1-height[i];
    num=sh[n];

    int tx,ty;
    int ans;
    if(qx==0)
    {
        if(qy>num){printf("-1\n");return 0;}
        find_(qy,tx,ty);
    }
    else 
    {
        ans=C();
        if(ans>num){printf("-1\n");return 0;}
        find_(ans,tx,ty);
    }
    for(int i=sa[tx];i<=sa[tx]+ty-1;i++)printf("%c",st[i-1]);
    printf("\n");

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值