求排名为K的子串

从前有这样一种问题,给我们一个字符串,让我们求其所有不相同的子串中按字典序排名为K的子串

在讨论这个问题的解法之前,不妨先看如何这样一个问题:如何求一个字符串的所有不相同的子串的个数?

很容易理解,一个字符串的任意一个子串都是母串的某个后缀的某个前缀,那么求一个字符串所有不同子串的个数就相当于求其所有后缀的不同前缀的个数,至此我们想到可以对一个字符串的所有后缀按字典序排序,这样拥有相同前缀的后缀就放到了相邻的位置,再计算每个后缀对答案的贡献即可。

先拿"HOMURA"这个字符串举个例子。

我们先对其所有后缀排序,得到如下结果:

排名后缀
1A
2HOMURA
3MURA
4OMURA
5RA
6URA

每个后缀的答案的贡献即为其前缀的数量,也就是其长度,那么"HOMURA"这个字符串的所有不同子串的数量即为N=1+6+4+5+2+3=21

再看下一个例子"ABABA"

排名后缀LCP
1A0
2ABA1
3ABABA3
4BA0
5BABA2
这次每个后缀的贡献不都是其长度了,因为它的一些前缀同时也是另一些后缀的前缀,我们只在每个前缀第一次出现是计算它们。对于这样一些已经排序的后缀,我们发现只在相邻后缀之间会有相同前缀,我们可以求出每个后缀与其排名前一个后缀之间的最长公共前缀的长度(LCP),每个后缀的贡献就是这个后缀的长度减去其LCP


至此,我们发现这样不仅可以求一个字符串的所有不同字串的个数,还能求出排名前K的所有后缀贡献出了多少不同的子串

这样我们就可以很容易地二分出排名为K的子串是哪一个后缀的第几个未被计算过的前缀,于是就可求出排名为K的子串

举个例子:求字符串"EXCITING"的所有不相同子串中字典序排名为20 的子串

排名后缀LCP贡献子串数sum
1CITING066
2EXCITING0814
3G0115
4ING0318
5ITING1422
6NG0224
7TING0428
8XCITING0735
可以算出排名的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;
	}
}



  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值