题目传送门
解题思路:
多组输入会RE(坑爹玩意儿)
后缀自动机上可以跑子串,因此每一位我们都按照a~z的取,这样可以使字典序尽量小。
怎样才能找到第k小的呢?
DFS预处理每个节点包括自己共可形成多少子串,记为cnt[]。注意记忆化,不然GG(复杂度大概从O(26^2N)->O(2N))
然后逼近即可,我们保证k小于等于当前子树大小,找到当前根的26个子节点中,第一个使得前pos棵子树cnt[]之和大于等于k,
然后我们只需在第pos棵子树中找第(k - 之前的0~pos-1的cnt和 -1)小的即可
(-1表示减去pos这个节点代表的串)
方法类似于平衡树找第k小
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define pt(x) printf("%s = %d\n",#x,x)
#define INF 0x3f3f3f3f
#define pb push_back
const int N = 9e4+5;
char s[N];
const int maxn = 9e4+5;
const int ALP = 26;
struct SAM
{
int trie[maxn<<1][ALP];
int fa[maxn<<1];
int len[maxn<<1];
int sz,last;
int cnt[maxn<<1];
void init(){
sz = last = 0;
newnode();
fa[0] = -1,len[0] = 0;
memset(cnt,0,sizeof cnt);
}
int newnode(){
memset(trie[sz],0,sizeof trie[sz]);
return sz++;
}
int idx(char ch){
return ch-'a';
}
void add(char ch){
int c = idx(ch);
int p = last,np = newnode();
last = np;
len[np] = len[p]+1;
for (;~p && !trie[p][c];p = fa[p]) trie[p][c] = np;
if (p==-1) fa[np] = 0;
else {
int q = trie[p][c];
if (len[q]==len[p]+1) fa[np] = q;
else {
int nq = newnode();
fa[nq] = fa[q];
len[nq] = len[p]+1;
for (int i=0;i<ALP;i++) trie[nq][i] = trie[q][i];
fa[np] = fa[q] = nq;
for (;~p && trie[p][c]==q;p = fa[p]) trie[p][c] = nq;
}
}
}
int dfs(int u){
if (cnt[u]) return cnt[u];///一定要记忆化搜索,把已经求好的找出来
if (u) cnt[u] = 1;
for (int c=0;c<ALP;c++){
if (trie[u][c]) cnt[u] += dfs(trie[u][c]);
}
return cnt[u];
}
void kth(int now,int k){
if (k==0) {puts("");return;}
for (int c = 0;c<ALP;c++){
int v = trie[now][c];
if (!v) continue;
k -= cnt[v];
if (k<=0){///证明在这个节点及其子树中
k += cnt[v];
k--;//减去当前节点
putchar(c+'a');
kth(v,k);
break;
}
}
}
}sam;
int main()
{
scanf("%s",s);
sam.init();
for (int i=0;s[i];i++) sam.add(s[i]);
sam.dfs(0);
int m;
scanf("%d",&m);
while (m--){
int k;
scanf("%d",&k);
sam.kth(0,k);
}
return 0;
}