P3975 [TJOI2015]弦论 (SAM)

题目链接

https://www.luogu.com.cn/problem/P3975

题意

给你一个仅由小写字母构成的字符串\(s\),输出它的第\(k\)小子串(\(t=0\)时表示不同位置的相同子串算作一个,\(t=1\)时表示不同位置的相同子串算作多个)

思路

构建出SAM之后,求出\(sum[i]\),表示有\(sum[i]\)个子串经过\(i\)号点。
\(siz[i]\)表示\(i\)所代表的\(endpos\)的集合大小,也就是\(i\)所对应字符串集合的出现次数。
\(t=0\)时,本质相同的子串在不同位置出现算相同,所以\(siz[i]=1\),即将每个字符串集合的\(endpos\)集合大小(字符串集合元素出现次数)置为\(1\)
\(t=1\)时,本质相同的子串在不同位置出现算不同,那么累加后的\(siz\)表示实际上\(endpos\)的集合大小
在SAM的树结构上\(dp\)\(siz\)
在SAM的图结构上\(dp\)\(sum\)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxx = 2*1e6+10;
char s[maxx];
int last=1,tot=1,fa[maxx],ch[maxx][26],len[maxx];
LL siz[maxx],sum[maxx];
void add(int x)
{
    int pre=last,now=last=++tot;
    len[now]=len[pre]+1;siz[now]=1;
    for(;pre&&!ch[pre][x];pre=fa[pre])ch[pre][x]=now;
    if(!pre)fa[now]=1;
    else
    {
        int q=ch[pre][x];
        if(len[q]==len[pre]+1)fa[now]=q;
        else
        {
            int nows=++tot;
            len[nows]=len[pre]+1;
            memcpy(ch[nows],ch[q],sizeof(ch[q]));
            fa[nows]=fa[q];fa[q]=fa[now]=nows;
            for(;pre&&ch[pre][x]==q;pre=fa[pre])ch[pre][x]=nows;
        }
    }
}

int head[maxx],to[maxx],ne[maxx],cnt;
int vis[maxx];
void addm(int u,int v)
{
    to[++cnt]=v,ne[cnt]=head[u],head[u]=cnt;
}
void dfs1(int u)
{
    for(int i=head[u];i;i=ne[i])
    {
        dfs1(to[i]);
        siz[u]+=siz[to[i]];
    }
}
void dfs2(int u)
{
    if(vis[u])return;
    vis[u]=1;
    for(int i=0;i<26;i++)
    {
        int v=ch[u][i];
        if(v)dfs2(v),sum[u]+=sum[v];
    }
}
void solve(int u,int k)
{
    if(k<=siz[u])return;
    k-=siz[u];
    for(int i=0;i<26;i++)
    {
        int v=ch[u][i];
        if(!v)continue;
        if(k>sum[v])k-=sum[v];
        else
        {
            putchar(i+'a');
            solve(v,k);
            return;
        }
    }
}
int main()
{
    scanf("%s",s+1);
    int t,k;
    scanf("%d%d",&t,&k);
    int n=strlen(s+1);
    for(int i=1;i<=n;i++)add(s[i]-'a');
    for(int i=2;i<=tot;i++)addm(fa[i],i);
    dfs1(1);
    for(int i=1;i<=tot;i++)
    {
        if(t)sum[i]=siz[i];
        else sum[i]=siz[i]=1;
    }
    sum[1]=siz[1]=0; //根的点权为0
    dfs2(1);
    if(sum[1]<k)puts("-1");
    else solve(1,k),puts("");
    return 0;
}
posted @ 2020-05-26 20:14  灰灰烟影  阅读(197)  评论(0编辑  收藏  举报