求排名爲K的子串

從前有這樣一種問題,給我們一個字符串,讓我們求其所有不相同的子串中按字典序排名爲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
這次每個後綴的貢獻不都是其長度了,因爲它的一些前綴同時也是另一些後綴的前綴,我們只在每個前綴第一次出現是計算它們。對於這樣一些已經排序的後綴,我們發現只在相鄰後綴之間會有相同前綴,我們可以求出每個後綴與其排名前一個後綴之間的最長公共前綴的長度(LCP),每個後綴的貢獻就是這個後綴的長度減去其LCP


至此,我們發現這樣不僅可以求一個字符串的所有不同字串的個數,還能求出排名前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
可以算出排名的20的子串是第5個後綴的第2個未出現過的前綴,也就是從LCP長度開始的第二位爲止的前綴,即ITI

代碼:

#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;
	}
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章