P3975 [TJOI2015]弦论 第K小子串

本文介绍了一种使用后缀自动机解决字符串子串问题的方法,包括构建后缀自动机、求解子串数目及按字典序遍历子串的过程。适用于寻找特定条件下第k小子串的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

https://www.luogu.org/problem/P3975

为了提高智商,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%的数据,n ≤ 1000。

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

对于100%的数据,n ≤ 5 × 10^5, t < 2, k ≤ 10^9。

利用后缀自动机求解

endpos 大小就是  有多少个这样的子串

按照parent 反拓扑序 求 sum [  i ]   (经过自动机每个节点的子串数目)

关于反拓扑序的解释

将所有的parent 反过来,我们就得到了parent树
如果要处理什么,就需要parent树的拓扑序
(因为parent相当于包含了所有的他的子树,都需要更新上去)
其实不需要拓扑排序
我们知道s 的endpos完全被parent 的endpos包含
s.longest一定长于parent.longest
所以,一个状态的longest越长,它一定要被更先访问
所以,按照longest 的长度进行桶排序就可以解决拓扑序了

求出来之后按照字典序遍历即可

参考

https://www.cnblogs.com/cjyyb/p/8446205.html

 

#include<cstring>
#include<cstdio>
#include<string>
#include<iostream>

using namespace std;
typedef long long ll;
const int maxn=5e5+5;

struct SAM
{
    int trans[maxn<<1][26],slink[maxn<<1],maxlen[maxn<<1];
    int last,now,root,len;
    int indegree[maxn<<1],endpos[maxn<<1],rank[maxn<<1],ans[maxn<<1];

    void newnode(int v)
    {
        maxlen[++now]=v;
    }
    void extend(int c)
    {
        newnode(maxlen[last]+1);
        int p=last,np=now;
        while(p&&!trans[p][c])
        {
            trans[p][c]=np;
            p=slink[p];
        }
        if(!p)
            slink[np]=root;
        else
        {
            int q=trans[p][c];
            if(maxlen[p]+1!=maxlen[q])
            {
                newnode(maxlen[p]+1);
                int nq=now;
                memcpy(trans[nq],trans[q],sizeof(trans[q]));
                slink[nq]=slink[q];
                slink[q]=slink[np]=nq;
                while(p&&trans[p][c]==q)
                {
                    trans[p][c]=nq;
                    p=slink[p];
                }
            }
            else
                slink[np]=q;
        }
        last=np;
        endpos[np]=1;
    }
    void build(string s)
    {
        root=last=now=1;
        memset(trans,0,sizeof(trans));
        memset(slink,0,sizeof(slink));
        memset(maxlen,0,sizeof(maxlen));
        len=s.size();
        for(int i=0;i<len;++i)
            extend(s[i]-'a');
    }
    inline void topsort()
    {
        for(int i=1;i<=now;++i)
            indegree[maxlen[i]]++;
        for(int i=1;i<=now;++i)
            indegree[i]+=indegree[i-1];
        for(int i=1;i<=now;++i)
            rank[indegree[maxlen[i]]--]=i;
        for(int i=now;i>=1;--i)
        {
            int x=rank[i];
            endpos[slink[x]]+=endpos[x];
        }
    }
}sam;

ll sum[maxn<<1];
void f(int x,int k)
{
    if(k<=sam.endpos[x])
        return ;
    k-=sam.endpos[x];
    for(int i=0;i<26;i++)
    {
        if(sam.trans[x][i])
        {
            if(k>sum[sam.trans[x][i]])
            {
                k-=sum[sam.trans[x][i]];
                continue;
            }
            else
            {
                printf("%c",i+'a');
                f(sam.trans[x][i],k);
                return ;
            }
        }
    }
}
int main()
{
    string s;
    cin>>s;
    int t,k;
    cin>>t>>k;
    sam.build(s);
    sam.topsort();
    if(t==0)
    {
        for(int i=sam.root;i<=sam.now;i++)
        {
            sum[i]=1;
            sam.endpos[i]=1;
        }
    }
    else if(t==1)
    {
        for(int i=sam.root;i<=sam.now;i++)
        {
            sum[i]=sam.endpos[i];
        }
    }
    sum[1]=0;
    sam.endpos[1]=0;
    for(int i=sam.now;i>=sam.root;i--)
    {
        for(int j=0;j<26;j++)
        {
            if(sam.trans[sam.rank[i]][j])
                sum[sam.rank[i]]+=sum[sam.trans[sam.rank[i]][j]];
        }
    }
    if(sum[sam.root]<k)
        printf("-1");
    else
        f(1,k);
    return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值