码迷,mamicode.com
首页 > 其他好文 > 详细

[TJOI2015]弦论

时间:2019-07-22 16:46:19      阅读:94      评论:0      收藏:0      [点我收藏+]

标签:long   code   记忆化   getchar   char   情况   ble   fine   gis   

Link


很经典的一道后缀自动机裸体

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

先说T=0

我们回归right集合的本质,它表示的是结束位置相同的子串集合,那么right集合的大小就表示当前结束位置相同的子串的个数,等价于当前已经匹配到的字符串的出现次数。因为相同子串只算一次,所以直接把所有的right集合大小赋值为1即可。

T=1

同T=0,直接建出Parent树,在树上dfs一遍求出所有right集合大小(当然还可以倒序for一遍,因为是一棵树,不会有后效性)

但求出了right集合大小还是不够的,我们需要考虑另外一个数组sum,表示的是right集合在后缀自动机意义下的前缀和,sum[u]就表示以一个后缀自动机上,编号为u的节点作为前缀的子串个数(同文章开头待求的值)。因为后缀自动机建出来是一个DAG,所以可以使用拓扑排序(本人用的是记忆化深搜)

还是见代码吧(里面有注释)

#include<stdio.h>
#include<string.h>
#define rint register int 
#define N 2000007

template<class T>
inline void read(T &x){
    x=0;char c=getchar();T flag=1;
    while(c<'0'||c>'9'){if(c=='-')flag=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
    x*=flag;
}

struct E{
    int next,to;
}e[N];
struct Node{//SAM 
    int c[27],len,par;
}nd[N];

char s[N];
int n,tot=1,root=1,last=1,head[N],cnt=0,opt;
long long right[N],sum[N],rk;

inline int max(int x,int y){return x>y? x:y;}
inline void add(int id,int to){
    e[++cnt]=(E){head[id],to};
    head[id]=cnt;
}
inline void insert(int w){
    int tmp=last,X=last=++tot;right[tot]=1;
    nd[X].len=nd[tmp].len+1;
    for(;tmp&&!nd[tmp].c[w];tmp=nd[tmp].par) nd[tmp].c[w]=X;
    if(!tmp) nd[X].par=root;
    else{
        int B=nd[tmp].c[w];
        if(nd[B].len==nd[tmp].len+1) nd[X].par=B;
        else{
            int nb=++tot;
            nd[nb]=nd[B];
            nd[B].par=nd[X].par=nb;
            nd[nb].len=nd[tmp].len+1;
            for(;tmp&&nd[tmp].c[w]==B;tmp=nd[tmp].par) nd[tmp].c[w]=nb;
        }
    }
}
void dfs(int u){
    if(!opt) right[u]=1;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        dfs(v);
        if(opt) right[u]+=right[v];
    }
}
int dfs_(int u){
    if(sum[u]!=-1) return sum[u];
    sum[u]=right[u];
    for(int i=0;i<26;i++)
        if(nd[u].c[i]) sum[u]+=dfs_(nd[u].c[i]);
    return sum[u];
}
void print(int u,long long k){
    if(k<=right[u]) return;
    k-=right[u];
    for(int i=0;i<26;i++)
        if(nd[u].c[i]){
            int v=nd[u].c[i];
            if(k>sum[v]){
                k-=sum[v];
                continue;
            }
            printf("%c",i+'a');
            print(v,k);
            return;
        }
}
int main(){
    scanf("%s",s);
    read(opt),read(rk);
    n=strlen(s);
    memset(sum,-1,sizeof(sum));
    for(rint i=0;i<n;++i) insert(s[i]-'a');
    for(rint i=2;i<=tot;++i) add(nd[i].par,i);//建Parent树 
    dfs(1);right[1]=0;dfs_(1);
    if(sum[1]<rk) printf("-1");
    else print(1,rk);
}

[TJOI2015]弦论

标签:long   code   记忆化   getchar   char   情况   ble   fine   gis   

原文地址:https://www.cnblogs.com/wwlwQWQ/p/11226604.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!