【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; }