从前有这样一种问题,给我们一个字符串,让我们求其所有不相同的子串中按字典序排名为K的子串
在讨论这个问题的解法之前,不妨先看如何这样一个问题:如何求一个字符串的所有不相同的子串的个数?
很容易理解,一个字符串的任意一个子串都是母串的某个后缀的某个前缀,那么求一个字符串所有不同子串的个数就相当于求其所有后缀的不同前缀的个数,至此我们想到可以对一个字符串的所有后缀按字典序排序,这样拥有相同前缀的后缀就放到了相邻的位置,再计算每个后缀对答案的贡献即可。
先拿"HOMURA"这个字符串举个例子。
我们先对其所有后缀排序,得到如下结果:
排名 | 后缀 |
1 | A |
2 | HOMURA |
3 | MURA |
4 | OMURA |
5 | RA |
6 | URA |
每个后缀的答案的贡献即为其前缀的数量,也就是其长度,那么"HOMURA"这个字符串的所有不同子串的数量即为N=1+6+4+5+2+3=21
再看下一个例子"ABABA"
排名 | 后缀 | LCP |
1 | A | 0 |
2 | ABA | 1 |
3 | ABABA | 3 |
4 | BA | 0 |
5 | BABA | 2 |
至此,我们发现这样不仅可以求一个字符串的所有不同字串的个数,还能求出排名前K的所有后缀贡献出了多少不同的子串
这样我们就可以很容易地二分出排名为K的子串是哪一个后缀的第几个未被计算过的前缀,于是就可求出排名为K的子串
举个例子:求字符串"EXCITING"的所有不相同子串中字典序排名为20 的子串
排名 | 后缀 | LCP | 贡献子串数 | sum |
1 | CITING | 0 | 6 | 6 |
2 | EXCITING | 0 | 8 | 14 |
3 | G | 0 | 1 | 15 |
4 | ING | 0 | 3 | 18 |
5 | ITING | 1 | 4 | 22 |
6 | NG | 0 | 2 | 24 |
7 | TING | 0 | 4 | 28 |
8 | XCITING | 0 | 7 | 35 |
代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
using namespace std;
const int maxn=100100;
const int INF=0x3f3f3f3f;
int a[maxn],b[maxn],c[maxn],d[maxn];
int s[maxn],n;
int sa[maxn],h[maxn],rk[maxn];
int sum[maxn];
char str[maxn];
bool cmp(int r[],int a,int b,int k){
return r[a]==r[b]&&r[a+k]==r[b+k];
}
void build_sa(int r[],int sa[],int n,int m){
int i,j,p,*x=a,*y=b;
for(i=0;i<m;c[i++]=0);
for(i=0;i<n;c[x[i++]=r[i-1]]++);
for(i=0;i<m;c[++i]+=c[i-1]);
for(i=n-1;i>=0;sa[--c[x[i]]]=i--);
for(j=1,p=1;p<n;j<<=1,m=p){
for(p=0,i=n-j;i<n;y[p++]=i++);
for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
for(i=0;i<n;d[i++]=x[y[i-1]]);
for(i=0;i<m;c[i++]=0);
for(i=0;i<n;c[d[i++]]++);
for(i=0;i<m;c[++i]+=c[i-1]);
for(i=n-1;i>=0;sa[--c[d[i--]]]=y[i+1]);
for(swap(x,y),p=1,x[sa[0]]=0,i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
void calh(int r[],int sa[],int n){
int i,j,k=0;
for(i=1;i<=n;rk[sa[i++]]=i-1);
for(i=0;i<n;h[rk[i++]]=k)
for(k?k--:0,j=sa[rk[i]-1];r[i+k]==r[j+k];k++);
}
int main(){
int Q;
scanf("%s",str);
n=strlen(str);
for(int i=0;i<n;i++)s[i]=(int)str[i];
s[n]=0;
build_sa(s,sa,n+1,256);
calh(s,sa,n);
int tot=0;
for(int i=1;i<=n;i++){
sum[i] = n-sa[i]-h[i];
tot+=sum[i];
}
for(int i=2;i<=n;i++)
sum[i]+=sum[i-1];
int kth,ans;
cin>>Q;
for(int i=1;i<=Q;i++){
cin>>kth;
if(kth>tot)kth%=tot;
int L=0,R=n,mid;
while(L<=R){
mid=(L+R)>>1;
if(sum[mid]>=kth){
ans=mid;R=mid-1;
}else L=mid+1;
}
int s=sa[ans];
int t=s+kth-sum[ans-1]+h[ans]-1;
for(int i=s;i<=t;i++)cout<<str[i];
cout<<endl;
}
}