2018.12.22【TJOI2015】【BZOJ3998】【洛谷P3975】弦论(后缀数组SA)

BZOJ传送门

洛谷传送门


解析;

后缀数组 O ( n ) O(n) O(n)解法,比后缀自动机 O ( n ∗ s i g m a ) O(n*sigma) O(nsigma)不知道高到哪里去了。

思路:

首先,要做到真正的 O ( n ) O(n) O(n),你需要会 O ( n ) O(n) O(n)构造后缀数组,然而 O ( n log ⁡ n ) O(n\log n) O(nlogn)也能过。

但是你要是 O ( n ) O(n) O(n)写的是 D C 3 DC3 DC3而不是 S A I S SAIS SAIS,你极有可能跑的比倍增还慢。

那么现在怎么 O ( n ) O(n) O(n)求第 k k k小字串呢?

二分?不,这样就不是 O ( n ) O(n) O(n)了。

考虑本质不同字串的情况,我们只需要在排好序的后缀上扫一遍,每一个后缀的前缀一定是排在前面的较小的字串。考虑下一个后缀有多少个新的前缀是前面没有出现过的。就是 n − h t i − s a i + 1 n-ht_i-sa_i+1 nhtisai+1个,这些就是新的排在前面的字串,一旦遇到超过 k k k了,那么以 s a i sa_i sai开头,长度为 h t i + k ht_i+k hti+k的字串就是答案。

考虑所有字串的情况
我们用一个单调栈维护当前后缀与排名在它前面的所有后缀的 L C P LCP LCP长度,这些后缀的覆盖长度就是字串个数。每次把重复的抽走就行了。

当然实现起来也是有一定的技巧的,比如用邻接表。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

cs int N=500005;
namespace SA{
	char s[N];
	int sa[N],ht[N],rk[N],n;
	int st[N],b[N];
	bool t[N<<1];
	
	int val[N],len[N],nxt[N],last[N];
	
	inline bool islms(int i,bool *cs t);
	template<class T>
	inline void induced_sort(T s,int len,int siz,int sigma,bool *cs t,int *cs cb,int *cs p);
	template<class T>
	inline void sais(T s,int len,bool *cs t,int *cs b1,int sigma);
	inline void init();	
	inline void solve();
}

inline bool SA::islms(int i,bool *cs t){
	return i>0&&t[i]&&!t[i-1];
}

template<class T>
inline void SA::induced_sort(T s,int len,int siz,int sigma,bool *cs t,int *cs cb,int *cs p){
	memset(b,0,sizeof(int)*sigma);
	memset(sa,-1,sizeof(int)*len);
	for(int re i=0;i<len;++i)++b[s[i]];
	cb[0]=b[0];
	for(int re i=1;i<sigma;++i)cb[i]=cb[i-1]+b[i];
	for(int re i=siz-1;~i;--i)sa[--cb[s[p[i]]]]=p[i];
	for(int re i=1;i<sigma;++i)cb[i]=cb[i-1]+b[i-1];
	for(int re i=0;i<len;++i)if(sa[i]>0&&!t[sa[i]-1])sa[cb[s[sa[i]-1]]++]=sa[i]-1;
	cb[0]=b[0];
	for(int re i=1;i<sigma;++i)cb[i]=cb[i-1]+b[i];
	for(int re i=len-1;~i;--i)if(sa[i]>0&&t[sa[i]-1])sa[--cb[s[sa[i]-1]]]=sa[i]-1;
}


template<class T>
inline void SA::sais(T s,int len,bool *cs t,int *cs b1,int sigma){
	int *cb=b+sigma,siz=0,cnt=0,p=-1;
	t[len-1]=1;
	for(int re i=len-2;i>=0;--i)t[i]=s[i]==s[i+1]?(t[i+1]):(s[i]<s[i+1]);
	for(int re i=1;i<len;++i)if(t[i]&&!t[i-1])b1[siz++]=i;
	induced_sort(s,len,siz,sigma,t,cb,b1);
	for(int re i=siz=0;i<len;++i)if(islms(sa[i],t))sa[siz++]=sa[i];
	for(int re i=siz;i<len;++i)sa[i]=-1;
	for(int re i=0;i<siz;++i){
		re int x=sa[i];
		for(int re j=0;j<len;++j){
			if(p==-1||s[x+j]!=s[p+j]||t[x+j]!=t[p+j]){
				++cnt;
				p=x;
				break;
			}
			else if(j>0&&(islms(x+j,t)||islms(p+j,t)))break;
		}
		sa[siz+(x>>=1)]=cnt-1;
	}
	for(int re i=len-1,j=len-1;i>=siz;--i)if(sa[i]>=0)sa[j--]=sa[i];
	int *s1=sa+len-siz,*b2=b1+siz;
	if(cnt<siz)sais(s1,siz,t+len,b1+siz,cnt);
	else for(int re i=0;i<siz;++i)sa[s1[i]]=i;
	for(int re i=0;i<siz;++i)b2[i]=b1[sa[i]];
	induced_sort(s,len,siz,sigma,t,cb,b2);
}

inline void SA::init(){
	scanf("%s",s+1);
	n=strlen(s+1);
	s[n+1]='\0';
	sais(s+1,n+1,t,st,128);
	rk[0]=0;sa[0]=n+1;
	for(int re i=1;i<=n;++i)rk[++sa[i]]=i;
	for(int re i=1,k=0,j=0;i<n;ht[rk[i++]]=k)
	for(k?--k:0,j=sa[rk[i]-1];s[i+k]==s[j+k];++k);
}

inline void SA::solve(){
	re int op,k,top=0;
	scanf("%d%d",&op,&k);
	switch(op){
		case 0:{
			for(int re i=1;i<=n;++i){
				re int temp=n-sa[i]+1-ht[i];
				if(temp>=k){
					for(int re j=0;j<ht[i]+k;++j)pc(s[sa[i]+j]);
					return ;
				}
				else k-=temp;
			}
			puts("-1");
			break;
		}
		case 1:{
			re int *p=b,cnt=0;
			for(int re i=2;i<=n;++i){
				re int pos=i;
				while(top&&st[top]>=ht[i]){
					pos=p[top];
					if(st[top]>ht[i]){
						len[++cnt]=i-p[top];
						val[cnt]=st[top];
						nxt[cnt]=last[p[top]],last[p[top]]=cnt;
					}
					--top;
				}
				st[++top]=ht[i];
				p[top]=pos;
			}
			while(top&&st[top]>0){
				len[++cnt]=n-p[top]+1;
				val[cnt]=st[top];
				nxt[cnt]=last[p[top]];
				last[p[top]]=cnt;
				--top;
			}
			for(int re i=1;i<=n;++i){
				re int nowh=ht[i];
				for(int j=last[i+1];j;j=nxt[j]){
					int v=val[j]-nowh,l=len[j]+1,t=v*l;
					if(k>t)k-=t,nowh+=v;
					else {
						k=(k-1)/l+1;
						for(int re x=0;x<nowh+k;++x)pc(s[sa[i]+x]);
						return ;
					}
				}
				re int t=n-sa[i]+1-nowh;
				if(k>t)k-=t;
				else {
					for(int re j=0;j<nowh+k;++j)pc(s[sa[i]+j]);
					return ;
				}
			}
			puts("-1");
			break;
		}
	}
}

signed main(){
	SA::init();
	SA::solve();
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值