第一次看见这样强行套两道题的
首先字典序最小的子串肯定是在自动机上贪心
而如果我们第一步走最小的转移
那么第一个字符确定后无论剩下字符是什么,这些子串都绝对是最小的那些
第二小的转移类似
所以我们只需要在上统计每个点能到的子树大小(虽然这不是一棵树,但其实意思是一样的,毕竟这是一个)
其实可以按照排序后从小往大枚举上的点点,然后把每个点的所有转移的加给它自己
然后就像平衡树查找序列第大数一样
枚举字符从小到大每一个儿子的转移,如果就,走到第一个小等于的转移就走下去,因为要输出走法,所以在每次转移的时候输出转移字符就是了
注意两种情况的不同做法,时是统计上的
把所有非克隆的点赋为,排序后从大到小把加给
时则相当于是统计每个点的集合的大小
因为任意的,与的任意元素都互不相同
所以我们可以保证子树传上来一定就是自己的集合大小
时就不需要管出现过几次了,只需要把所有都赋为1就可以了
注意最后会在后缀自动机上跑一次,就是统计所有子串的数量
因为没理解自动机和的区别和意义想了好久
#include<bits/stdc++.h>
using namespace std;
#define ll long long
inline int read(){
char ch=getchar();
int res=0;
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))res=(res<<3)+(res<<1)+(ch^48),ch=getchar();
return res;
}
const int N=500005;
struct node{
int len,link,nxt[27];
};
#define max(a,b) (((a)>(b))?a:b)
node st[N<<1];
int tot,op,f[N<<1],t[N<<1],g[N<<1],k,siz[N<<1],last,ans;
char a[N];
inline void init(){
last=tot=1;
}
inline void sa_extend(int c){
int cur=++tot,p=last;last=cur;
st[cur].len=st[p].len+1,siz[cur]=1;
for(;p&&!st[p].nxt[c];p=st[p].link) st[p].nxt[c]=cur;
if(!p)st[cur].link=1;
else{
int q=st[p].nxt[c];
if(st[p].len+1==st[q].len)st[cur].link=q;
else {
int clo=++tot;
st[clo]=st[q];
st[clo].len=st[p].len+1;
for(;p&&st[p].nxt[c]==q;p=st[p].link)st[p].nxt[c]=clo;
st[q].link=st[cur].link=clo;
}
}
}
int main(){
scanf("%s",a+1),init();
int len=strlen(a+1);
for(int i=1;i<=len;i++)sa_extend(a[i]-'a');
op=read(),k=read();
for(int i=1;i<=tot;i++)
t[st[i].len]++;
for(int i=1;i<=tot;i++)
t[i]+=t[i-1];
for(int i=1;i<=tot;i++)
g[t[st[i].len]--]=i;
if(op){
for(int i=tot;i;i--)
siz[st[g[i]].link]+=siz[g[i]];
}
else{
for(int i=tot;i;i--)siz[g[i]]=1;
}
siz[1]=0;
for(int i=tot;i;i--){
f[g[i]]=siz[g[i]];
for(int j=0;j<26;j++){
if(st[g[i]].nxt[j])
f[g[i]]+=f[st[g[i]].nxt[j]];
}
}
if(k>f[1]){
cout<<-1;return 0;
}
int now=1;
k-=siz[now];
while(k){
int p=0;
while(k>f[st[now].nxt[p]])k-=f[st[now].nxt[p]],++p;
now=st[now].nxt[p],putchar('a'+p),k-=siz[now];
}
}