[Luogu P3975] [TJOI2015]弦论

洛谷传送门
BZOJ传送门

题目描述

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

输入输出格式

输入格式:

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

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

输出格式:

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

输入输出样例

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

说明

数据范围

对于10%的数据, n1000 n ≤ 1000

对于50%的数据, t=0 t = 0

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

解题分析

SAM S A M 的板题。 后缀自动机是个 DAG D A G , 所以我们构建出 parent p a r e n t 链后从下往上按拓扑序递推, 得到每个子串出现的次数, 再通过转移边做一遍类似前缀和的操作得到在每个点向下走会有多少个子串。

如果 t=0 t = 0 , 那么每个点的贡献都为 1 1 。 如果t=1, 那么贡献为 right r i g h t 集合大小。 因为我们在 insert i n s e r t 操作的时候分离出来的那个辅助点实际上和原来的点表示一个子串, 所以其 siz s i z 初始化为0,否则向上累加的时候会多算次数,但当 t=0 t = 0 的时候作为一个单独的子串其 siz=1 s i z = 1 。(具体画画图就知道了)。

代码如下:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <cctype>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define MX 2000500
char dat[MX];
int to[MX][26], par[MX], siz[MX], len[MX], buc[MX], id[MX], sum[MX];
int cnt, l, arr, last, cur, typ, k;
namespace SAM
{
    IN void insert(R int id)
    {
        R int now, tar;
        cur = ++cnt; len[cur] = len[last] + 1; siz[cur] = 1;
        for (now = last; ~now; now = par[now])
        {
            if(to[now][id]) break;
            to[now][id] = cur;
        }
        if(now < 0) return last = cur, par[cur] = 0, void();
        tar = to[now][id];
        if(len[tar] == len[now] + 1) return last = cur, par[cur] = tar, void();
        int nw = ++cnt; std::memcpy(to[nw], to[tar], sizeof(to[tar]));
        par[nw] = par[tar], par[tar] = nw; len[nw] = len[now] + 1;
        for (; (~now) && to[now][id] == tar; now = par[now]) to[now][id] = nw;
        par[cur] = nw; last = cur;
    }
    void calc()
    {
        for (R int i = 1; i <= cnt; ++i) ++buc[len[i]];
        for (R int i = 1; i <= l; ++i) buc[i] += buc[i - 1];
        for (R int i = 1; i <= cnt; ++i) id[buc[len[i]]--] = i;
        for (R int i = cnt; i; --i)
        {
            if(typ) siz[par[id[i]]] += siz[id[i]];
            else siz[id[i]] = 1;
        }
        siz[0] = 0;
        for (R int i = cnt; ~i; --i)
        {
            sum[id[i]] = siz[id[i]];
            for (R int j = 0; j < 26; ++j)
            if(to[id[i]][j]) sum[id[i]] += sum[to[id[i]][j]];
        }
    }
}
int main(void)
{
    par[0] = -1;
    scanf("%s", dat + 1);
    l = std::strlen(dat + 1);
    scanf("%d%d", &typ, &k);
    for (R int i = 1; i <= l; ++i) SAM::insert(dat[i] - 'a');
    SAM::calc();
    if(sum[0] < k) return puts("-1"), 0;
    R int now = 0, tar;
    W ((k -= siz[now]) > 0)
    {
        for (tar = 0; tar < 26; ++tar)
        {
            if(to[now][tar])
            {
                if(k <= sum[to[now][tar]]) break;
                k -= sum[to[now][tar]];
            }
        }
        now = to[now][tar];
        putchar('a' + tar);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值