题目:
题解:
因为后缀自动机可以识别串S的所有子串,所以它是解决这类问题十分方便的一个方法。我们可以顺着自动机这个有向无环图来DFS,对于每个节点预处理它往后还能经过多少不同的子串,再决定走哪条路就可以了:如果他的儿子的size>=k就说明第k小的子串的结尾在该子树中,否则k-size,然后从下个继续。有点主席树查询区间k大的意思。
这个预处理就相当于求DAG中一个节点往后还有多少不同的路径,是一个简单的DP,每次枚举当前节点的所有儿子它们累加过来就可以了。如果是相同子串不重复计算,每个点的初始权值就是1;否则每个点的初始权值就是它Right集合的大小。这种时候要特别注意输出的时候减去的也是Right集合的大小而不是1
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=1000005;
int p,q,np,nq,cnt,last,c[N],a[N],fa[N],step[N],ch[N*2][30],size[N],l,r[N];
char st[N];
void insert(int c)
{
p=last; last=np=++cnt;
step[np]=step[p]+1;
while (p && !ch[p][c]) ch[p][c]=np,p=fa[p];
if (!p) {fa[np]=1;return;}
q=ch[p][c];
if (step[q]==step[p]+1){fa[np]=q; return;}
nq=++cnt; step[nq]=step[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q]; fa[q]=fa[np]=nq;
while (ch[p][c]==q) ch[p][c]=nq,p=fa[p];
}
void calc0()
{
for (int i=cnt;i;i--)
{
int t=a[i];
if (t!=1) size[t]++;
for (int j=0;j<26;j++) size[t]+=size[ch[t][j]];
}
}
void calc1()
{
p=1;
for (int i=0;i<l;i++)
{
p=ch[p][st[i]-'a'];
r[p]++;
}
for (int i=cnt;i;i--)
{
int t=a[i];
r[fa[t]]+=r[t];
}
for (int i=cnt;i;i--)
{
int t=a[i];
if (t!=1) size[t]=r[t];
for (int j=0;j<26;j++) size[t]+=size[ch[t][j]];
}
}
void print(int t,int k)
{
p=1;
while (k)
{
for (int i=0;i<26;i++)
if (size[ch[p][i]]>=k)
{
printf("%c",i+'a'); p=ch[p][i];
if (t==0) k--;else k-=r[p];
break;
}
else k-=size[ch[p][i]];
}
}
int main()
{
scanf("%s",st);
last=cnt=1; l=strlen(st);
for (int i=0;i<l;i++) insert(st[i]-'a');
int t,k;scanf("%d%d",&t,&k);
for (int i=1;i<=cnt;i++) c[step[i]]++;
for (int i=1;i<=cnt;i++) c[i]+=c[i-1];
for (int i=1;i<=cnt;i++) a[c[step[i]]--]=i;
if (t==0) calc0();
else calc1();
if (size[1]<k) printf("-1");
else print(t,k);
}