[BZOJ3998][TJOI2015]弦论(后缀自动机)

=== ===

这里放传送门

=== ===

题解

求后缀自动机上的第K小子串,基本思路是建立自动机以后递推出从每个节点出发还能到达多少子串,通过这个值来判断在每个节点的时候需要走哪一个儿子。对于这个题来说type=0或=1的时候有一点点区别。type为0的时候相同的子串算作一个,那么就只需要统计在自动机上从当前节点出发有多少条不同的路径就可以了,实际上就是一个拓扑图上的递推;type为1的时候相同的子串算作多个,那么每个节点贡献的路径数目就不只是1而是它Right集合的大小。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int len,tot,b[1100010],d[1100010],T,K;
char s[500010],ans[500010];
struct Node{
    Node *ch[30],*fa;
    int step;
    long long res,cnt;
    Node();
}*null,*Root,*t[1100010],P[1100010],*last,*p,*q,*np,*nq;
Node::Node(){
    for (int i=0;i<=26;i++) ch[i]=null;
    fa=null;step=res=cnt=0;
}
Node *New(){return t[++tot];}
void insert(int c){
    p=last;np=last=New();
    np->step=p->step+1;
    while (p->ch[c]==null&&p!=null){
        p->ch[c]=np;p=p->fa;
    }
    if (p==null){np->fa=Root;return;}
    q=p->ch[c];
    if (q->step==p->step+1){np->fa=q;return;}
    nq=New();nq->step=p->step+1;
    memcpy(nq->ch,q->ch,sizeof(q->ch));
    nq->fa=q->fa;q->fa=np->fa=nq;
    while (p->ch[c]==q){p->ch[c]=nq;p=p->fa;}
}
void getord(){
    for (int i=1;i<=tot;i++) ++b[t[i]->step];
    for (int i=1;i<=tot;i++) b[i]+=b[i-1];
    for (int i=tot;i>=1;i--)
      d[b[t[i]->step]--]=i;
}
void getres(){
    for (int i=tot;i>=1;i--){
        Node *v=t[d[i]];
        v->res++;v->cnt=1;
        for (int j=0;j<26;j++)
          v->res+=v->ch[j]->res;
    }
}
void getcnt(){
    Node *ptr=Root;
    for (int i=0;i<len;i++){
        ptr=ptr->ch[s[i]-'a'];
        ptr->cnt++;
    }
    for (int i=tot;i>=1;i--){
        Node *v=t[d[i]];
        v->fa->cnt+=v->cnt;
    }
    for (int i=tot;i>=1;i--){
        Node *v=t[d[i]];
        v->res=v->cnt;//用cnt来递推能够到达的重复子串的个数
        for (int j=0;j<26;j++)
          v->res+=v->ch[j]->res;
    }
}
void search(Node *now,int k){
    if (k<=0) return;
    for (int i=0;i<26;i++)
      if (now->ch[i]!=null){
          Node *v=now->ch[i];
          if (k<=v->res){
              ans[++len]=i+'a';
              search(v,k-v->cnt);break;//注意转移的时候要减去当前的cnt
          }else k-=v->res;
      }
}
int main()
{
    null=new Node;*null=Node();
    gets(s);len=strlen(s);
    for (int i=0;i<=2*len+10;i++){
        P[i]=Node();t[i]=P+i;
    }
    last=Root=New();
    for (int i=0;i<len;i++) insert(s[i]-'a');
    getord();
    scanf("%d%d",&T,&K);
    if (T==0) getres();
    else getcnt();
    len=0;search(Root,K);
    if (len==0) printf("-1");
    for (int i=1;i<=len;i++) printf("%c",ans[i]);
    printf("\n");
    return 0;
}

偏偏在最后出现的补充说明

好久不做后缀自动机好多东西都忘得差不多了。。
常见的几种递推比如Right集合啊,还有这个题的能到达多少子串啊之类的。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值