BZOJ 3998 弦论(后缀自动机+dfs)

3998: [TJOI2015]弦论

Time Limit: 10 Sec   Memory Limit: 256 MB
Submit: 3779   Solved: 1375
[ Submit][ Status][ Discuss]

Description

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

Input

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

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

Output

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

Sample Input

aabc

0 3

Sample Output

aab

HINT

 N<=5*10^5


T<2

K<=10^9




       相对来说比较简单的一道题目。
      求排名第k大的子串。显然,既然是所有子串,那么显然用后缀自动机,这个可以在里面保存所有子串的东西。然后就是考虑第k大。我们知道自动机里面的每一个节点表示一个状态,它的最大长度对应一个子串。然后所有的parent指针指向它的状态都是包含它的。所以说,如果那些状态出现,这个状态一定会出现,因此可以对于T不同的情况讨论。如果T等于0,那么只要它在parent树中有儿子或者本事就是关键点,那么该点表示的串出现次数为1;如果T等于1,那么该点表示串出现次数为本身的值与儿子出现次数之 和。
      如此,我们求出来了每个点表示串的出现次数,有些人称之为right数组。然后我们考虑如何解决这个K大的问题。后缀自动机本身就是包含字典树的存储方式,所以想要做到字典序也是很简单。所以,如果对于每一个状态,我们知道与该点前缀相同的所有子串个数,那么就可以用dfs定位这个第k大。而求这个前缀相同的子串个数,也是很容易的,对于一个节点直接求它所有儿子的right数组之和即可,注意这个儿子不是parent树中的儿子,而是自动机中的后继边。之后的dfs就很简单了。还有关于实现方法,用基数排序之后的序列从后往前,是符合dfs顺序且这么做比dfs快。具体见代码:
#include<bits/stdc++.h>
#define N 1000010
using namespace std;

int t,k,tot,n,cnt[N],sum[N];
char s[N];

struct Suffix_Automation
{
    int tot,cur,c[N],sa[N];
    struct node{int ch[26],len,fa;} T[N];
    void init(){cur=tot=1;memset(T,0,sizeof(T));}

    void ins(int x,int id)
    {
        int p=cur;cur=++tot;T[cur].len=id; cnt[cur]++;
        for(;p&&!T[p].ch[x];p=T[p].fa) T[p].ch[x]=cur;
        if (!p) {T[cur].fa=1;return;}int q=T[p].ch[x];
        if (T[p].len+1==T[q].len) {T[cur].fa=q;return;}
        int np=++tot; memcpy(T[np].ch,T[q].ch,sizeof(T[q].ch));
        T[np].fa=T[q].fa; T[q].fa=T[cur].fa=np; T[np].len=T[p].len+1;
        for(;p&&T[p].ch[x]==q;p=T[p].fa) T[p].ch[x]=np;
    }

    void getsa()
    {
        for(int i=2;i<=tot;i++) c[T[i].len]++;
        for(int i=1;i<=n;i++) c[i]+=c[i-1];
        for(int i=tot;i>=1;i--) sa[c[T[i].len]--]=i;
    }

} SAM;

void find_kth(int x,int k)
{
    if (k<=cnt[x]) return;
    k-=cnt[x];
    for(int i=0;i<26;i++)
    {
        if (int y=SAM.T[x].ch[i])
        {
            if (k<=sum[y])
            {
                putchar(i+'a');
                find_kth(y,k);
                return;
            } else k-=sum[y];
        }
    }
}

int main()
{
    SAM.init();
    scanf("%s",s);
    scanf("%d%d",&t,&k);
    for(int i=0;s[i];i++)
        SAM.ins(s[i]-'a',i+1);
    n=strlen(s); SAM.getsa();
    for(int i=SAM.tot-1;i>=0;i--)
    {
        if (!t) cnt[SAM.sa[i]]=1;
        cnt[SAM.T[SAM.sa[i]].fa]+=cnt[SAM.sa[i]];
    }
    cnt[1]=0;
    for(int i=SAM.tot-1;i>=0;i--)
    {
        sum[SAM.sa[i]]=cnt[SAM.sa[i]];
        for(int j=0;j<26;j++)
            sum[SAM.sa[i]]+=sum[SAM.T[SAM.sa[i]].ch[j]];
    }
    if (sum[1]<k){puts("-1");return 0;}
    find_kth(1,k);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值