洛谷 P3975 [TJOI2015]弦论 后缀自动机

题目描述

为了提高智商,ZJY开始学习弦论。这一天,她在《String theory》中看到了这样一道问题:对于一个给定的长度为n的字符串,求出它的第k小子串是什么。你能帮帮她吗?

输入输出格式

输入格式:
第一行是一个仅由小写英文字母构成的字符串s

第二行为两个整数t和k,t为0则表示不同位置的相同子串算作一个,t为1则表示不同位置的相同子串算作多个。k的意义见题目描述。

输出格式:
输出数据仅有一行,该行有一个字符串,为第k小的子串。若子串数目不足k个,则输出-1。

输入输出样例

输入样例#1:
aabc
0 3
输出样例#1:
aab
输入样例#2:
aabc
1 3
输出样例#2:
aa
输入样例#3:
aabc
1 11
输出样例#3:
-1
说明

数据范围

对于 10% 10 % 的数据, n1000 n ≤ 1000
对于 50% 50 % 的数据, t=0 t = 0
对于 100% 100 % 的数据, n5×105,t<2,k109 n ≤ 5 × 10 5 , t < 2 , k ≤ 10 9

分析:
我们可以对这个串建后缀自动机。如果 t=0 t = 0 ,每个点的 size s i z e 1 1 ,否则为right集大小。因为每一条路径都可以代表一个字符串,我们在 DAG D A G 上跑出每个点为前缀的字符串有多少个。然后像权值线段树求第 k k 大一样选择走哪条边,不过要先减掉当前点的size 1 1 号节点的size不用减,因为不存在这样的串。

代码:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <queue>

const int maxn=1e6+7;

using namespace std;

char s[maxn],ans[maxn];
int n,T,k,cnt,num;
int size[maxn],top[maxn],b[maxn],sum[maxn];

queue <int> q;

struct node{
    int len,fail;
    int son[26];
}t[maxn];

void build_sam()
{
    cnt=1;
    int now=1,p,q,clone;
    for (int i=0;i<n;i++)
    {
        int c=s[i]-'a';
        p=now;
        now=++cnt;
        t[now].len=t[p].len+1;
        size[now]=1;
        while (p&&(!t[p].son[c])) t[p].son[c]=now,p=t[p].fail;
        if (!p) t[now].fail=1;
        else
        {
            q=t[p].son[c];
            if (t[p].len+1==t[q].len) t[now].fail=q;
            else
            {
                clone=++cnt;
                t[clone]=t[q];
                t[clone].len=t[p].len+1;
                t[now].fail=t[q].fail=clone;
                while (p&&(t[p].son[c]==q)) t[p].son[c]=clone,p=t[p].fail;
            }
        }
    }
}

void prework()
{
    for (int i=1;i<=cnt;i++) b[t[i].len]++;
    for (int i=1;i<=n;i++) b[i]+=b[i-1];
    for (int i=1;i<=cnt;i++) top[b[t[i].len]--]=i;
    for (int i=cnt;i>0;i--) size[t[top[i]].fail]+=size[top[i]];
    for (int i=cnt;i>0;i--)
    {
        int x=top[i];
        if (!T) size[x]=1;
        sum[x]=size[x];
        for (int j=0;j<26;j++) sum[x]+=sum[t[x].son[j]];
    }
}

void solve()
{
    q.push(1);
    while (!q.empty())
    {
        int x=q.front();
        q.pop();
        if (x!=1)
        {
            if (k<=size[x]) return;
            k-=size[x];
        }
        for (int i=0;i<26;i++)
        {
            if (sum[t[x].son[i]]>=k)
            {
                ans[++num]='a'+i;
                q.push(t[x].son[i]);
                break;
            }
            else k-=sum[t[x].son[i]];
        }
    }

}

int main()
{
    scanf("%s",s);
    scanf("%d%d",&T,&k);
    n=strlen(s);
    build_sam();    
    prework();      
    solve();
    if (!num) printf("-1");
    else
    {
        for (int i=1;i<=num;i++) printf("%c",ans[i]);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值