首页 > 代码库 > 【BZOJ-3998】弦论 后缀自动机

【BZOJ-3998】弦论 后缀自动机

3998: [TJOI2015]弦论

Time Limit: 10 Sec  Memory Limit: 256 MB
Submit: 2018  Solved: 662
[Submit][Status][Discuss]

Description

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

Input

 第一行是一个仅由小写英文字母构成的字符串S

第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个。T=1则表示不同位置的相同子串算作多个。K的意义如题所述。

Output

输出仅一行,为一个数字串,为第K小的子串。如果子串数目不足K个,则输出-1

Sample Input

aabc
0 3

Sample Output

aab

HINT

N<=5*10^5

T<2
K<=10^9

Source

Solution

后缀自动机的裸题?不过给我挺大帮助的。

建出后缀自动机求K大的问题,先拓扑排序/基数排序,然后递推出每个节点能到的子串数,然后dfs一遍加加减减。

这个题在递推的时候讨论一下即可,T=0时说明每个状态代表一个子串(除空串以外),T=1时每个节点的Parent树的子树中的节点数都是可以得到的子串数,所以需要累加。

而这个累加的过程,可以理解成是求出$Right$集合的大小,所以构建时的新建节点显然不能重复计算。

然后dfs一遍,类似于线段树上二分的思想,输出答案。

自己没有写递归的写法,直接用的while里非递归。

Code

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>using namespace std;#define MAXN 500010char A[MAXN],ans[MAXN];int N,T,K;namespace SAM{    int son[MAXN<<1][27],par[MAXN<<1],len[MAXN<<1],root,last,sz,size[MAXN<<1];    inline void Init() {root=sz=last=1;}    inline void Extend(int c)    {        int cur=++sz,p=last;        len[cur]=len[p]+1; size[cur]=1;        while (p && !son[p][c]) son[p][c]=cur,p=par[p];        if (!p) par[cur]=root;        else            {                int q=son[p][c];                if (len[p]+1==len[q]) par[cur]=q;                else                    {                        int nq=++sz;                        memcpy(son[nq],son[q],sizeof(son[nq]));                         len[nq]=len[p]+1;                        par[nq]=par[q];                        while (p && son[p][c]==q) son[p][c]=nq,p=par[p];                        par[cur]=par[q]=nq;                    }            }        last=cur;    }    inline void Build() {Init(); for (int i=1; i<=N; i++) Extend(A[i]-a+1);}    int st[MAXN],id[MAXN<<1],sum[MAXN<<1];    inline void Pre()    {        for (int i=1; i<=sz; i++) st[len[i]]++;        for (int i=1; i<=N; i++) st[i]+=st[i-1];        for (int i=1; i<=sz; i++) id[st[len[i]]--]=i;        if (!T)            {                for (int i=sz; i>=1; i--) size[i]=1;                size[root]=0;                for (int i=sz,Sum=0; i>=1; i--,Sum=0)                    {                        for (int j=1; j<=26; j++)                            Sum+=sum[son[id[i]][j]];                        sum[id[i]]=Sum+1;                    }            }        else            {                for (int i=sz; i>=1; i--)                    size[par[id[i]]]+=size[id[i]];                size[root]=0;                for (int i=sz; i>=1; i--)                    {                        sum[id[i]]=size[id[i]];                        for (int j=1; j<=26; j++)                            sum[id[i]]+=sum[son[id[i]][j]];                    }            }    }    inline void Query(int K)    {        int now=root,tot=0;        while (K)            {                for (int i=1; i<=26; i++)                    if (son[now][i])                        if (sum[son[now][i]]>=K)                            {                                ans[++tot]=a+i-1;                                K-=size[son[now][i]];                                now=son[now][i];                                break;                            }                        else K-=sum[son[now][i]];             }        ans[++tot]=0;    }}using namespace SAM;int main(){    scanf("%s",A+1);    N=strlen(A+1);    SAM::Build();    scanf("%d%d",&T,&K);    SAM::Pre();    if (sum[root]<K)        puts("-1");    else        Query(K),puts(ans+1);    return 0;}

 

【BZOJ-3998】弦论 后缀自动机