[bzoj3998][SAM]弦论

8 篇文章 0 订阅

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

题解

SAM入门题,巩固了一下对于right集合构造的理解
首先对原串建一个SAM,然后通过拓扑序把right集合构建出来
for(int i=1;i<=cnt;i++)Rsort[tr[i].dep]++;
for(int i=1;i<=len;i++)Rsort[i]+=Rsort[i-1];
for(int i=cnt;i>=1;i--)sa[Rsort[tr[i].dep]--]=i;

这三行是一个优化的拓扑序,用了基数排序的思想
你可以贪心地想一想,原串中len越大的点,他们的拓扑序就在越后面。然后对于编号越大的点,拓扑序会比len相同但是编号小一点的点的拓扑序大
处理T不同的情况
对于T=0,right集合就搞成1,因为你出现的次数是right,但是相同的看成一个,我们就把他搞成1就好了
对于T=1,right集合不变
之后处理一下这个点出来能跑出多少不同的串,在SAM上搜一下即可

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
struct SAM
{
    int son[30],dep,parent;
}tr[1110000];int cnt,last,root;
char ch[510000];
void add(int k)
{
    int x=ch[k]-'a';
    int p=last,np=++cnt;
    tr[np].dep=k;
    while(p && tr[p].son[x]==0)tr[p].son[x]=np,p=tr[p].parent;
    if(p==0)tr[np].parent=root;
    else
    {
         int q=tr[p].son[x];
         if(tr[q].dep==tr[p].dep+1)tr[np].parent=q;
         else
         {
            int nq=++cnt;
            tr[nq]=tr[q];tr[nq].dep=tr[p].dep+1;
            tr[q].parent=tr[np].parent=nq;
            while(p && tr[p].son[x]==q)tr[p].son[x]=nq,p=tr[p].parent;
         }
    }
    last=np;
}
int T,k;
int Rsort[1110000],right[1110000],sa[1110000],sum[1110000];
void dfs(int x)
{
    if(k<=right[x])return ;
    k-=right[x];
    for(int i=0;i<=25;i++)
        if(tr[x].son[i]!=0)
        {
            int y=tr[x].son[i];
            if(k>sum[y])k-=sum[y];
            else {printf("%c",i+'a');dfs(y);return ;}
        }
}
int main()
{
    root=last=cnt=1;
    scanf("%s",ch+1);int len=strlen(ch+1);
    for(int i=1;i<=len;i++)add(i);
    scanf("%d%d",&T,&k);
    for(int i=1;i<=cnt;i++)Rsort[tr[i].dep]++;
    for(int i=1;i<=len;i++)Rsort[i]+=Rsort[i-1];
    for(int i=cnt;i>=1;i--)sa[Rsort[tr[i].dep]--]=i;
    for(int i=1,p=root;i<=len;i++)
    {
        int x=ch[i]-'a';
        p=tr[p].son[x];
        right[p]++;
    }
    for(int i=cnt;i>=1;i--)
    {
        if(T==1)right[tr[sa[i]].parent]+=right[sa[i]];
        else right[sa[i]]=1;
    }
    right[root]=0;
    for(int i=cnt;i>=1;i--)
    {
        int now=sa[i];sum[now]=right[now];
        for(int j=0;j<=25;j++)
            if(tr[now].son[j]!=0)sum[now]+=sum[tr[now].son[j]];
    }
    if(k>sum[root])printf("-1\n");
    else dfs(root);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值