题意:给出一个串,求出所有子串,去重,然后求字典序第K小的串,总共Q组询问。
题解:对串创建SAM,然后,拓扑一下,算出每个状态,如果继续往后边走,能够生成多少种不同的字符串。然后每次查询的时候,就按照字典顺序来看,比如到达某个点S,先看'a'儿子能造出多少个字符,如果很少,那么我们下一个字符肯定不走‘a’,那么我们继续看'b',看一下'a'和‘b’共能造出多少个字符,如果多了,那就选‘b’,如果还不够,那就继续看‘a’、‘b’、‘c’,在这个DAG上边爬一爬就行了。具体细节看一下代码。
Code:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 9e4+1000;
int len;
char s[maxn];
int cntA[maxn];
vector<char> ans;
struct SAM{
int last,cnt,nxt[maxn*2][26],fa[maxn*2],l[maxn*2];
int rk[maxn*2],num[maxn*2];
void init(){
last = cnt=1;
memset(nxt[1],0,sizeof nxt[1]);
fa[1]=l[1] =0;
}
void add(int c){
int p = last;
int np = ++cnt;
last = np;
l[np] =l[p]+1;
while (p&&!nxt[p][c]){
nxt[p][c] = np;
p = fa[p];
}
if (!p){
fa[np] =1;
}else{
int q = nxt[p][c];
if (l[q]==l[p]+1){
fa[np] =q;
}else{
int nq = ++cnt;
memcpy(nxt[nq],nxt[q],sizeof nxt[q]);
fa[nq] =fa[q];
l[nq] = l[p]+1;
fa[np] =fa[q] =nq;
while (nxt[p][c]==q){
nxt[p][c]=nq;
p = fa[p];
}
}
}
}
void build (){
for (int i=1;i<=cnt;i++){
cntA[l[i]]++;
}
for (int i=1;i<maxn-5;i++){
cntA[i]+=cntA[i-1];
}
for (int i=1;i<=cnt;i++){
rk[cntA[l[i]]--] =i;
}
for (int i=1;i<=cnt;i++){
num[i]=1;
}
for (int i=cnt;i>=1;i--){
int x = rk[i];
for (int i=0;i<26;i++){
if (nxt[x][i]){
num[x]+=num[nxt[x][i]];
}
}
}
num[0]=0;
}
inline void print(){
for (char t:ans){
printf("%c",t);
}
printf("\n");
}
void query(int K){
ans.clear();
int now=1;
int sum=0;
while (true){
// cout<<now<<' '<<sum<<endl;
if (sum==K){
print();
return ;
}
int c=0;
int last =0;
while (sum<K){
sum+=num[nxt[now][c]];
c++;
assert(c<27);
}
c--;
sum-=num[nxt[now][c]];
now = nxt[now][c];
ans.push_back('a'+c);
sum++;
}
}
}sam;
int main(){
int Q;
scanf("%s%d",s,&Q);
sam.init();
int le = strlen(s);
for (int i=0;i<le;i++){
sam.add(s[i]-'a');
}
sam.build();
while (Q--){
int x;
scanf("%d",&x);
sam.query(x);
}
return 0;
}