BZOJ 3998 [TJOI2015]弦论 后缀数组

Description

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

Input

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

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

Output

输出仅一行,为一个数字串,为第K小的子串。如果子串数目不足K个,则输出-1

Sample Input

aabc
0 3

Sample Output

aab

HINT

 N<=5*10^5


T<2

K<=10^9





后缀自动机做这题似乎才是正解……
但是我不会后缀自动机啊= =

第一问的话后缀数组经典的做法。
按照rank下来,每次一个新的后缀产生了len-height[i]-sa[i]+1个新的子串。
比如
aaa
aabbb
那么当i=2,aabbb产生了(5-2-sa[2]+1)个新的子串。
用这个思想一直累加上去,直到K为止即可。

第二问我不太会……
他的思路就是枚举每一位是什么,然后二分判断K是否合法。
这一部分直接copy了他的代码……
我感觉会有更小渐进的后缀数组做法但是目前为止先放放吧。

这题竟然没T卡过了……
下次会了sam再来看看……


#include<bits/stdc++.h>
#define ll long long 
using namespace std;
const int 
	N=500005;
char s[N];
int n,ans1,ans2,T,K;
int cnta[N],cntb[N],a[N],b[N<<1];
int tsa[N],sa[N],rank[N],height[N];
ll sum[N];
void Get_SA(){
	for (int i=0;i<=25;i++) cnta[i]=0;
	for (int i=1;i<=n;i++) cnta[s[i]-97]++;
	for (int i=1;i<=25;i++) cnta[i]+=cnta[i-1];
	for (int i=n;i;i--) sa[cnta[s[i]-97]--]=i;
	rank[sa[1]]=1;
	for (int i=2;i<=n;i++)
		rank[sa[i]]=rank[sa[i-1]]+(s[sa[i]]!=s[sa[i-1]]);
	for (int j=1;rank[sa[n]]!=n;j<<=1){
		for (int i=1;i<=n;i++) a[i]=rank[i],b[i]=rank[i+j];
		for (int i=0;i<=n;i++) cnta[i]=cntb[i]=0;
		for (int i=1;i<=n;i++) cnta[a[i]]++,cntb[b[i]]++;
		for (int i=1;i<=n;i++) cnta[i]+=cnta[i-1],cntb[i]+=cntb[i-1];
		for (int i=n;i;i--) tsa[cntb[b[i]]--]=i;
		for (int i=n;i;i--) sa[cnta[a[tsa[i]]]--]=tsa[i];
		rank[sa[1]]=1;
		for (int i=2;i<=n;i++)
			rank[sa[i]]=rank[sa[i-1]]+(a[sa[i]]!=a[sa[i-1]] || b[sa[i]]!=b[sa[i-1]]);
	}
}
void Get_H(){
	int len=0;
	for (int i=1;i<=n;i++){
		if (len) len--;
		while (s[i+len]==s[sa[rank[i]-1]+len]) len++;
		height[rank[i]]=len;
	}
}
void solve1(){
	for (int i=1;i<=n;i++){
		int tmp=n-height[i]-sa[i]+1;
		if (K>tmp) K-=tmp;
			else{
				ans1=sa[i];
				ans2=sa[i]+height[i]+K-1;
				return;
			}
	}
}
void solve2(){
	sum[0]=(ll)0;
	for (int i=1;i<=n;i++)
		sum[i]=sum[i-1]+(ll)(n-sa[i]+1);
	if (sum[n]<K) return;
	int l1=1,r1=n,l2;
    for (int i=1;i<=n;i++){
    	l2=l1;
        for (int j='a';j<='z';j++){
            int l=l2,r=r1;
            while (l<=r){
                int mid=(l+r)>>1;
                if (s[sa[mid]+i-1]>j) r=mid-1;
                	else l=mid+1;
            }
            ll t=sum[r]-sum[l2-1]-(ll)(r-l2+1)*(ll)(i-1);
            if (t>=K){
                if (K<=r-l2+1){
					ans1=sa[l2];
					ans2=sa[l2]+i-1;
					return;
				}
                l1=l2,r1=r;
				K-=r-l2+1;
				break;
            }
            l2=r+1,K-=t;
        }
        if (n-sa[l1]+1==i) l1++;
    }
}
int main(){
	scanf("%s",s+1);
	n=strlen(s+1);
	scanf("%d%d",&T,&K);
	
	Get_SA(),Get_H();
	
	ans1=ans2=-1;
	if (!T) solve1();
		else solve2();
	if (ans1==-1){puts("-1");return 0;}
	
	for (int i=ans1;i<=ans2;i++)
		putchar(s[i]);
	putchar('\n');
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值