【BZOJ】3998: [TJOI2015]弦论

【题意】给定长度为n的小写字母字符串S,求第k小子串。n<=5*10^5。

给定T,T=0时不同位置的相同子串算一个,T=1时算多个。

【算法】后缀自动机

【题解】对S建立SAM,T=0则每个节点算1次,T=1则每个节点算Right次,那么第k小就是dfs到恰好前k个的位置就是答案(SAM自带字典序)。

DFS前,我们需要每个点的Right数组大小,每个子树(trans边)的Right数组之和。

首先使所有前缀开端节点Right=1(即每次的np),然后对所有点按len进行基数排序从而保证拓扑序,然后从后往前统计。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1000010;
struct tree{int len,fa, t[30];}t[maxn];
int n,last,T,k,tot,right[maxn],sum[maxn],root,w[maxn],b[maxn];
char s[maxn];
void insert(int c){
    int np=++tot;
    t[np].len=t[last].len+1;
    right[np]=1;
    int x=last;
    while(x&&!t[x].t[c])t[x].t[c]=np,x=t[x].fa;
    last=np;
    if(!x)t[np].fa=root;else{
        int y=t[x].t[c];
        if(t[y].len==t[x].len+1)t[np].fa=y;else{
            int nq=++tot;
            t[nq]=t[y];
            t[nq].len=t[x].len+1;
            t[nq].fa=t[y].fa;t[y].fa=t[np].fa=nq;
            while(x&&t[x].t[c]==y)t[x].t[c]=nq,x=t[x].fa;
        }
    }
}
void dfs(int x){
    if(k<=right[x])return;
    k-=right[x];
    for(int i=0;i<26;i++)if(t[x].t[i]){
        int y=t[x].t[i];
        if(k>sum[y])k-=sum[y];else{
            putchar(i+'a');
            dfs(y);
            return;
        }
    }
}
int main(){
    scanf("%s",s+1);n=strlen(s+1);
    root=tot=last=1;
    for(int i=1;i<=n;i++)insert(s[i]-'a');
    scanf("%d%d",&T,&k);
    for(int i=1;i<=tot;i++)w[t[i].len]++;
    for(int i=1;i<=n;i++)w[i]+=w[i-1];
    for(int i=1;i<=tot;i++)b[w[t[i].len]--]=i;
    for(int j=tot;j>=2;j--){
        int i=b[j];
        if(T)right[t[i].fa]+=right[i];else right[i]=1;
    }
    right[root]=0;
    for(int o=tot;o>=1;o--){
        int i=b[o];sum[i]=right[i];
        for(int j=0;j<26;j++)if(t[i].t[j])sum[i]+=sum[t[i].t[j]];
    }
    if(k>sum[root])printf("-1");else dfs(root);
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/onioncyc/p/8125287.html