P3975 [TJOI2015]弦论

题意:

给出两个整数 t t t , k k k t t t为0则表示不同位置的相同子串算作一个, t t t 1 1 1则表示不同位置的相同子串算作多个。让你求出字符串的第 k k k小子串是什么。

题解:

我们首先只考虑如何输出第k小的子串,很显然,有一个性质,首位字符越小,子串排名越靠前,我们可以考虑一个字符一个字符的枚举,若第k小的子串是以当前字符开头的,那么就往下走,否则枚举下一个字符,再减去这个字符开头的子串个数。也就是说,我们现在的问题变成了统计以后缀自动机上某一个节点开头的子串的个数,这个问题就变得简单了。

对于 t = 0 t=0 t=0的情况,我们知道 e n d p o s endpos endpos集合表示的是结束位置的子串集合,那么 e n d p o s endpos endpos集合的大小就表示当前结束位置相同的子串的个数等价于当前已经匹配到的字符串的出现次数。因为相同子串只算一次,所以只需要把所有的 e n d p o s endpos endpos集合大小赋值为1即可。

对于 t = 1 t=1 t=1的情况,我们可以建出 p a r e n t parent parent树,然后基数排序,倒序 f o r for for循环跑一遍求出所有 e n d p o s endpos endpos集合大小。考虑用另外一个数组sum,表示的是 e n d p o s endpos endpos集合在后缀自动机意义下的前缀和, s u m [ u ] sum[u] sum[u]表示以一个在后缀自动机上,编号为u的节点作为前缀的子串个数。

A C AC AC代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6+50;
inline int read(){
    int x=0,f=1; char ch=getchar();
    while(!isdigit(ch)){ if(ch=='-') ch=-1; ch = getchar(); }
    while(isdigit(ch)) x=x*10+ch-48,ch=getchar();
    return x*f;
}
char s[MAXN];
struct Suffix{
    int nxt[MAXN][26],fa[MAXN],len[MAXN],sz[MAXN],sum[MAXN];
    int c[MAXN],a[MAXN];
    int last=1,tot=1;
    inline void Insert(int x){
        int p = last,np = ++tot;
        last = np,len[np] = len[p] + 1;
        for(;p && !nxt[p][x];p=fa[p]) nxt[p][x] = np;
        if(!p) fa[np] = 1;
        else {
            int q = nxt[p][x];
            if(len[p]+1 == len[q]) fa[np] = q;
            else {
                int nq = ++tot;
                len[nq] = len[p] + 1;
                memcpy(nxt[nq],nxt[q],sizeof(nxt[q]));
                fa[nq] = fa[q];
                fa[q] = fa[np] = nq;
                for(;nxt[p][x]==q;p=fa[p])
                    nxt[p][x] = nq;
            }
        }
        sz[np] = 1;
    }
    inline void Solve(int f,int k){
        for(int i=1;i<=tot;i++) c[len[i]]++;
        for(int i=1;i<=tot;i++) c[i]+=c[i-1];
        for(int i=1;i<=tot;i++) a[c[len[i]]--]=i;
        for(int i=tot;i;i--)
            if(!f) sz[a[i]] = 1;
            else sz[fa[a[i]]] += sz[a[i]];
        sz[1]=0;
        for(int i=tot;i;i--){
            sum[a[i]] = sz[a[i]];
            for(int j=0;j<26;j++)
                sum[a[i]] += sum[nxt[a[i]][j]];
        }
        if(k > sum[1]) { puts("-1");return; }
        int u = 1;
        while(k>0){
            int p = 0;
            while(k>sum[nxt[u][p]])
                k -= sum[nxt[u][p]],p++;
            u = nxt[u][p];
            putchar(p+'a');
            k -= sz[u];
        }
        return;
    }
}SAM;
int main(){
    scanf("%s",s+1);
    int f = read(),n = read();
    for(int i=1;s[i];i++) SAM.Insert(s[i]-'a');
    SAM.Solve(f,n);
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值