题解 SP7258 【SUBLEX - Lexicographical Substring Search】

devout

2020-12-22 19:55:53

Solution

**description** 给定一个字符串,求本质不同的第 $k$ 小的子串 **solution** 我们先对于这个字符串建立 SAM。 那么考虑SAM上的每个点,起点到他的路径上对应的就是一个唯一的子串。所以对于每一个点,我们可以求出以这个点对应的状态为前缀的子串的个数有多少。 求法就是对于 maxlen 从大到小排序,然后对于每个点 $u$,$siz[u]=1+\sum siz[v]$ 然后我们考虑类似于平衡树求第 $k$ 大的做法,我们从小到大枚举当前点的出边,如果 $k>siz[v]$,那么 $k=k-siz[v]$,考虑下一个出边,直到第一个 $k<siz[v]$,就往这里走。 复杂度 $\mathcal O(nq)$ 注意开始的时候我们不能用dfs/bfs去处理这个东西,因为SAM的自动机上的边其实是一个图,所以会出现问题。 **code** ```cpp # include <bits/stdc++.h> using namespace std; # define Rep(i,a,b) for(register int i=a;i<=b;i++) # define _Rep(i,a,b) for(register int i=a;i>=b;i--) # define RepG(i,u) for(int i=head[u];~i;i=e[i].next) typedef long long ll; const int N=1e5+5; template<typename T> void read(T &x){ x=0;int f=1; char c=getchar(); for(;!isdigit(c);c=getchar())if(c=='-')f=-1; for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+c-'0'; x*=f; } int n,q; char s[N]; int link[N<<1],sam[N<<1][26],maxlen[N<<1]; int siz[N<<1]; int lst=1,tot=1; struct misaka{ int len,id; bool operator < (const misaka &cmp)const{ return len<cmp.len; } }a[N<<1]; void insert(int x){ int now=++tot,p; maxlen[now]=maxlen[lst]+1; for(p=lst;p&&!sam[p][x];p=link[p])sam[p][x]=now; if(!p)link[now]=1; else{ int q=sam[p][x]; if(maxlen[q]==maxlen[p]+1)link[now]=q; else{ int clone=++tot; link[clone]=link[q]; maxlen[clone]=maxlen[p]+1; memcpy(sam[clone],sam[q],sizeof(sam[q])); for(;p&&sam[p][x]==q;p=link[p])sam[p][x]=clone; link[q]=link[now]=clone; } } lst=now; } int main() { scanf("%s",s+1); n=strlen(s+1); Rep(i,1,n)insert(s[i]-'a'); Rep(i,1,tot)a[i]=(misaka){maxlen[i],i}; sort(a+1,a+tot+1); _Rep(i,tot,1){ int u=a[i].id; siz[u]=1; Rep(i,0,25){ int v=sam[u][i]; if(!v)continue; siz[u]+=siz[v]; } } read(q); while(q--){ int k; read(k); int u=1; k++; while(k>1){ k--; Rep(i,0,25){ int v=sam[u][i]; if(!v)continue; if(k<=siz[v]){ printf("%c",i+'a'); u=v; break; } else k-=siz[v]; } } puts(""); } return 0; } ```