给定字符串S,Q个询问,每次询问第k大的字符串(去重)
思路:首先我们知道自动机里存了所有不同的子串,如果我们询问第k大直接dfs找是肯定要炸的,所以考虑在每个结点处先预处理出来经过这条路能找到几个字符串,然后在找第k大的时候就可以直接判断走不走这条路径。
几点细节:每个结点基础就会有一个路径,因为直接走一个字符能到这个结点;找第k大的时候会有一些+1或者-1的操作,因为起始节点是空字符串。
AC代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
char s[maxn];
int len;
struct SAM
{
int mp[maxn][30];int fa[maxn];int ed;int ct;int len[maxn];
//mp是SAM的图,fa是那颗树
//ed是insert之前最后的那个结点
//ct是用来计数的,跟建trie树里的那个tot一个性质
//len[i]表示i结点的maxlen,minlen不用记录,直接能算
SAM(){ed=ct=1;}
inline void ins(int c)
{
int p=ed;//p相当于不算这个新建的点是最后一个点,算上新建的这个点是倒数第二个点
ed=++ct;
len[ed]=len[p]+1;//先初始化size和len
for(;p&&mp[p][c]==0;p=fa[p]){
mp[p][c]=ed;
}//然后顺着parent树的路径向上找
if(p==0){//第一种情况:根作为其父节点
fa[ed]=1;
return;
}
int q=mp[p][c];
if(len[p]+1==len[q]){//第二种情况:别的结点作为其父节点
fa[ed]=q;
return;
}
len[++ct]=len[p]+1;//第三种情况:加上天上的点
fa[ct]=fa[q];
fa[q]=ct;
fa[ed]=ct;
for(int i=0;i<26;i++){
mp[ct][i]=mp[q][i];
}
for(int i=p;mp[i][c]==q;i=fa[i]){
mp[i][c]=ct;
}
}
}sam;
int cnt[maxn];
int dfs1(int u)
{
//cout<<u<<endl;
if(cnt[u]) return cnt[u];
cnt[u]=1;
for(int i=0;i<26;i++){
if(!sam.mp[u][i]) continue;
//cout<<u<<" "<<sam.mp[u][i]<<endl;
cnt[u]+=dfs1(sam.mp[u][i]);
}
return cnt[u];
}
void dfs2(int k,int u)
{
k--;
if(!k){
puts("");
return;
}
for(int i=0;i<26;i++){
if(!sam.mp[u][i]) continue;
if(cnt[sam.mp[u][i]]<k){
k-=cnt[sam.mp[u][i]];
continue;
}
putchar(i+'a');
dfs2(k,sam.mp[u][i]);
break;
}
}
int main()
{
scanf("%s",s+1);
len=strlen(s+1);
for(int i=1;i<=len;i++){sam.ins(s[i]-'a');}
dfs1(1);
int q;scanf("%d",&q);
int num;
while(q--){
scanf("%d",&num);
dfs2(num+1,1);
}
return 0;
}