BZOJ 3998 弦论 TJOI2015

Problem

BZOJ

Solution

建立后缀自动机,然后我们可以用类似二叉搜索树的思想。先排出拓扑序,然后将这个节点所代表的子串的个数处理出来,然后枚举第k小的是否在其中即可。
我们不妨令g[u]表示从节点u开始能代表的后缀个数,那么可以做一个拓扑动规。
我们容易得到方程:
g [ u ] = s z [ u ] + u > v g [ v ]
注意在计算重复子串时,要sz[f[i]]+=sz[i]。

Code

#include <cstring>
#include <cstdio>
using namespace std;
const int maxn=1000010;
int t,k,n,lst,cnt,now=1,len,pre;
int a[maxn],b[maxn],c[maxn],f[maxn],l[maxn],sz[maxn],ch[maxn][26],g[maxn];
char s[maxn>>1];
void insert(int c)
{
    int p=lst,np=++cnt;
    lst=np;l[np]=l[p]+1;sz[np]=1;
    for(;p&&!ch[p][c];p=f[p]) ch[p][c]=np;
    if(!p) f[np]=1;
    else
    {
        int q=ch[p][c];
        if(l[q]==l[p]+1) f[np]=q;
        else
        {
            int nq=++cnt;l[nq]=l[p]+1;if(!t) sz[nq]=1;
            memmove(ch[nq],ch[q],sizeof(ch[q]));
            f[nq]=f[q];f[q]=f[np]=nq;
            for(;ch[p][c]==q;p=f[p]) ch[p][c]=nq;
        }
    }
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    #endif
    scanf("%s%d%d",s+1,&t,&k);
    n=strlen(s+1);lst=cnt=1;
    for(int i=1;i<=n;i++) insert(s[i]-'a');
    for(int i=1;i<=cnt;i++) c[l[i]]++;
    for(int i=1;i<=cnt;i++) c[i]+=c[i-1];
    for(int i=1;i<=cnt;i++) a[c[l[i]]--]=i;
    for(int i=cnt,j;t&&i;i--){j=a[i];sz[f[j]]+=sz[j];}
    sz[1]=sz[0]=0;
    for(int i=cnt,j;i;i--)
    {
        j=a[i];g[j]=sz[j];
        for(int r=0;r<26;r++)
          g[j]+=g[ch[j][r]];
    }
    if(g[1]<k){puts("-1");return 0;}
    while(k>sz[now])
    {
        k-=sz[now];
        for(int i=0;i<26;i++)
        {
            if(k>g[ch[now][i]]) k-=g[ch[now][i]];
            else{putchar(i+'a');now=ch[now][i];break;}
        }
    }
    putchar('\n');
    return 0;
}

猜你喜欢

转载自blog.csdn.net/as_a_kid/article/details/80188172