Problem
对于一个给定的长度为 n n n 的字符串,求出它的第 k k k 小子串。
还有一个参数 t t t, t t t 为 0 0 0 则表示不同位置的相同子串算作一个, t t t 为 1 1 1 则表示不同位置的相同子串算作多个。
Solution
我们令 S i z e i Size_i Sizei 为 p a r e n t _ t r e e \mathrm {parent\_tree} parent_tree 上 i i i 子树的节点个数(除去克隆点)。
S i z e i Size_i Sizei 有一个性质,即 S i z e i Size_i Sizei 与 i i i 所在的终点等价类的 e n d p o s \mathrm{endpos} endpos 集合大小相等。
因此当 t = 1 t=1 t=1 时 S i z e i Size_i Sizei 保持不变, t = 0 t=0 t=0 时将每个 S i z e i Size_i Sizei 设为 1 1 1(代表只算一次)。
于是我们就可以通过 S i z e Size Size 求出子串的总数,从而用试填发法的思想确定第 k k k 小字符串。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1000005
#define ll long long
using namespace std;
char S[N];
int l,t,last,tot;
int A[N],Size[N];
ll k,sum[N];
struct SAM{
int len,link,nxt[30];
}a[N<<1];
void Insert(int c){
int p,cur=++tot;
a[cur].len=a[last].len+1,Size[cur]=1;
for(p=last;p!=-1&&!a[p].nxt[c];p=a[p].link) a[p].nxt[c]=cur;
if(p==-1) a[cur].link=0;
else{
int now=a[p].nxt[c];
if(a[now].len==a[p].len+1)
a[cur].link=now;
else{
int Clone=++tot;
a[Clone]=a[now],a[Clone].len=a[p].len+1;
for(;p!=-1&&a[p].nxt[c]==now;p=a[p].link)
a[p].nxt[c]=Clone;
a[cur].link=a[now].link=Clone;
}
}
last=cur;
}
void solve(int x,int k){
if(k<=Size[x]) return;
k-=Size[x];
for(int i=0;i<26;++i){
int to=a[x].nxt[i];
if(!to) continue;
if(k>sum[to]) {k-=sum[to];continue;}
putchar(i+'a'),solve(to,k);return;
}
}
int main(){
a[0].link=-1;
scanf("%s%d%lld",S+1,&t,&k);
int i,j,l=strlen(S+1);
for(i=1;i<=l;++i) Insert(S[i]-'a');
for(i=1;i<=tot;++i) sum[a[i].len]++;
for(i=1;i<=tot;++i) sum[i]+=sum[i-1];
for(i=1;i<=tot;++i) A[sum[a[i].len]--]=i;
for(i=tot;i>=0;--i)
(t?(Size[a[A[i]].link]+=Size[A[i]]):(Size[A[i]]=1)),sum[A[i]]=Size[A[i]];
sum[0]=Size[0]=0;
for(i=tot;i>=0;--i)
for(j=0;j<26;++j)
if(a[A[i]].nxt[j])
sum[A[i]]+=sum[a[A[i]].nxt[j]];
if(sum[0]<k) puts("-1");
else solve(0,k);
return 0;
}