dtoi2572 弦论(string)

题意:

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

题解:

     对于本题,首先我们要做的事情是先建立后缀自动机。

     如果T=0,那么每一个位置的出现次数直接设为1,T=1否则就是正常的right集合大小。

     那么我们可以再记一个sum[i],表示从i出发能走多少个串。

     那么我们只需要从a~z依次枚举下一个位置的字母,然后类似于可持久化线段树那样判断一下k是否小于等于sum[nex[i]],如果是就走它,否则k减掉他,继续枚举下一个字母,当k<=当前位置代表的字符串出现的次数的时候停下来即可。

#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cstdio>
using namespace std;
char s[500002];
int cur=1,cnt=1,n,t,k,last,ch[1000002][27],fa[1000002],dis[1000002],q[500002],st[1000002];
long long sum[1000002],v[1000002];
bool fl[1000002];
void build(int c,int id){
    last=cur;cur=++cnt;
    int p=last;dis[cur]=id;
    for (;p&&!ch[p][c];p=fa[p])ch[p][c]=cur;
    if (!p)fa[cur]=1;
    else
    {
        int q=ch[p][c];
        if (dis[q]==dis[p]+1)fa[cur]=q;
        else
        {
            int nt=++cnt;dis[nt]=dis[p]+1;
            for (int i=0;i<26;i++)
            ch[nt][i]=ch[q][i];
            fa[nt]=fa[q];fa[q]=fa[cur]=nt;
            for (;ch[p][c]==q;p=fa[p])ch[p][c]=nt;
        }
    }
    v[cur]=1;
}
void dfs(int x){
    if (fl[x])return;
    fl[x]=1;sum[x]=v[x];
    for (int i=0;i<26;i++)
    if (ch[x][i]){dfs(ch[x][i]);sum[x]+=sum[ch[x][i]];}
}
int main()
{
    scanf("%s%d%d",s+1,&t,&k);n=strlen(s+1);
    for(int i=1;i<=n;i++)build(s[i]-'a',i);
    for (int i=1;i<=cnt;i++)q[dis[i]]++;
    for (int i=1;i<=n;i++)q[i]+=q[i-1];
    for (int i=1;i<=cnt;i++)st[q[dis[i]]--]=i;
    for (int i=cnt;i>=1;i--)
    {
        if (t==1)v[fa[st[i]]]+=v[st[i]];
        else v[i]=1;
    }
    v[1]=0;dfs(1);
    if (sum[1]<k){puts("-1");return 0;}
    int rt=1;
    while(1)
    {
        if (k<=v[rt])break;else k-=v[rt];
        bool u=0;int son;
        for (int i=0;i<26;i++)
        if (ch[rt][i])
        {
            u=1;
            if (sum[ch[rt][i]]<k)k-=sum[ch[rt][i]];
            else {printf("%c",i+'a');son=ch[rt][i];break;}
        }
        if (!u)break;
        rt=son;
    }
    return 0;
}
原文地址:https://www.cnblogs.com/1124828077ccj/p/12302915.html