BZOJ3998[TJOI2015]弦论

Description

对于一个给定长度为$N$的字符串,求它的第$K$小子串是什么。

[1]不同位置的相同子串算作$1$个;
[2]不同位置的相同子串算作多个;

Input

第一行是一个仅由小写英文字母构成的字符串$S$

第二行为两个整数$T$和$K$,$T$为$0$则表示不同位置的相同子串算作一个。$T=1$则表示不同位置的相同子串算作多个。$K$的意义如题所述。

Output

输出仅一行,为一个数字串,为第$K$小的子串。如果子串数目不足$K$个,则输出$-1$
 
 
题解
模拟赛里写的时候本蒟蒻并不会后缀自动机的做法,然而后缀数组的方法我还是会的...
雅礼集训水箱一题和2018洛谷7月月赛beautiful pair这两题的类似分治一样的方法的总结,这种思路对我而言还是很好想到并实现的。
 
首先建出后缀数组...
 
然后,按排名找到两两之间最小的$height$值,如果相同取排名更靠前的,这样也就找出了最长的每一个后缀都有的一个相同的前缀$S$。由于后缀数组的性质,这个$S$的每一个前缀一定恰好是所有子串中字典序最小的那一部分字符串。这时候这一做法比后缀自动机更优的地方就体现出来了,对于$T=0$和$T=1$的差异,唯一的差别是,当$T=0$时,$S$的每一个前缀都对答案有$1$的贡献,当$T=1$时,$S$的每一个前缀都对答案有$N$的贡献。如果我们发现这些$S$的前缀不足以成为第$K$大的子串,那么就将$K$减去所有这些$S$的前缀的贡献之和,再优先递归处理排名更靠前的区间,接着再处理另一个区间。
 
注意,递归时要新增一参数$last$,表示上一级处理出的$S$为$S'$,因为当我们处理出当前区间的$S$时,有意义的部分只有每一个长度大于$|S'|$的$S$的前缀,因为$S'$的前缀已经在上一级被统计过了。
 
复杂度证明:
和水箱那题的方法如出一辙,不过为了保持新鲜感,还是换个方法。
后缀数组本身是$O(N\cdot log(N))$,使用线段树维护$height$最小值出现的位置,每次查询为$O(log(N))$,一共查询的次数是长度大于一的区间,每个区间由两个子区间构成。想象一下,初始有$N$个长度为$1$的区间,每次选出两个合并,产生一个新的长度大于$1$的区间,同时总的区间的数量$-1$,这样一共会产生$N-1$个长度大于$1$的区间。而一定有一种合并的方案,使得每次产生出的区间恰好对应每一个要查询的区间,这样的区间恰好有$N-1$个,所以复杂度$O(N\cdot log(N))$。
于是总复杂度$O(N\cdot log(N))$。
 
然而模拟赛中,写$init$函数和$solve$函数的顺序看着别扭,全选了一个函数在Dev C++ 5.1.1中用$Ctrl+Shift+\uparrow \downarrow$进行整体上移下移,随后又瞎按了几下$Ctrl+Z$(撤销)和$Ctrl+Y$(重做)导致后缀数组的某一个部分出现了两遍最后交上去极其惨烈......
 
AC代码如下
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define mid (l+r>>1) 
#define M 500020
using namespace std;
int read(){
	int nm=0,fh=1;char cw=getchar();
	for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
	for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
	return nm*fh;
}
int n,m=124,rk[M],sa[M],pos[M],c[M],height[M],T,K;
int p[M<<2];
char ch[M];
void finish(int l,int r){
	for(int i=l;i<=r;i++) putchar(ch[i]);
	putchar('\n'); exit(0);
}
void build(int x,	int l,int r){
	if(l==r){p[x]=l;return;}
	build(x<<1,l,mid),build(x<<1|1,mid+1,r);
	int t1=p[x<<1],t2=p[x<<1|1];
	if(height[t1]<=height[t2]) p[x]=t1;
	else p[x]=t2;
}
int query(int x,int l,int r,int L,int R){
	if(L<=l&&r<=R) return p[x];
	if(R<l||r<L) return 1;
	int t1=query(x<<1,l,mid,L,R);
	int t2=query(x<<1|1,mid+1,r,L,R);
	if(height[t1]<=height[t2]) return t1;
	else return t2;
}
void init(){
	scanf("%s",ch+1),n=strlen(ch+1),T=read(),K=read();
	for(int i=1;i<=n;i++) c[rk[i]=ch[i]]++;
	for(int i=2;i<=m;i++) c[i]+=c[i-1];
	for(int i=n;i>=1;i--) sa[c[rk[i]]--]=i;
	for(int k=1;k<=n;k<<=1){
		int tot=0;
		for(int i=n-k+1;i<=n;i++) pos[++tot]=i;
		for(int i=1;i<=n;i++) if(sa[i]>k) pos[++tot]=sa[i]-k; 
		for(int i=1;i<=m;i++) c[i]=0;
		for(int i=1;i<=n;i++) c[rk[i]]++;
		for(int i=2;i<=m;i++) c[i]+=c[i-1];
		for(int i=n;i>=1;i--) sa[c[rk[pos[i]]]--]=pos[i];
		swap(pos,rk),tot=rk[sa[1]]=1;
		for(int i=2;i<=n;i++) rk[sa[i]]=(pos[sa[i]]==pos[sa[i-1]]&&pos[sa[i]+k]==pos[sa[i-1]+k]?tot:++tot);
		if((m=tot)==n) break;
	}
	for(int i=1,k=0;i<=n;i++){
		if(k) k--;
		if(rk[i]==1) continue;
		for(int j=sa[rk[i]-1];i+k<=n&&j+k<=n&&ch[j+k]==ch[i+k];k++);
		height[rk[i]]=k;
	}
	height[1]=n,build(1,1,n);
}
void solve(int l,int r,int last){
	if(l==r){
		if(K<=n-sa[l]+1-last) finish(sa[l],sa[l]+last+K-1);
		else{K-=n-sa[l]+1-last;return;}
	}
	int now=query(1,1,n,l+1,r),res=T==0?1:r-l+1;
	int dt=res*(height[now]-last);
	if(K>dt&&dt>=0) K-=dt,solve(l,now-1,height[now]),solve(now,r,height[now]);
	else{for(K>res;K-=res,last++;); finish(sa[r],sa[r]+last);}
}
int main(){init(),solve(1,n,0),puts("-1"); return 0;}
posted @ 2018-08-13 21:16  OYJason  阅读(147)  评论(0编辑  收藏  举报