從前有這樣一種問題,給我們一個字符串,讓我們求其所有不相同的子串中按字典序排名爲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;
}
}