[DarkBZOJ4310]跳蚤

89 篇文章 0 订阅
13 篇文章 0 订阅

题目

传送门 to DarkBZOJ

思路

首先有这样一个事实,如果两个字符串 a , b a,b a,b 满足 a < b a<b a<b ,那么二者的相同长度的前缀 a ′ , b ′ a',b' a,b 满足 a ′ ≤ b ′ a'\le b' ab 。这是显而易见的。同时,如果这个长度超过了二者的 l c p lcp lcp ,还会有 a ≤ b ′ a\le b' ab

另一个事实是前缀小于原串,也就是 a ′ ≤ a a'\le a aa ,这也是毋庸置疑的。所以,如果长度超过 l c p lcp lcp ,我们有

a ′ ≤ a < b ′ ≤ b a'\le a<b'\le b aa<bb

那么我们先二分一个后缀,让大于这个后缀的都滚蛋。字典序比它小的后缀,肯定始终比它小;字典序比它大的,不能留下超过 l c p lcp lcp 的长度。所以我们会得到一些区间,限制条件是区间内至少切一刀。

怎么实现 “ 区间内至少切一刀 ” 的条件呢?这种题大家想必做的很多了,就是贪心,按照右端点排序,而后从左往右填。说白了就是,只有不得不切的时候才切。在本题中就比较容易实现。

所以我们找到一个字典序最小的后缀 a a a 使得最终答案不超过 a a a 。假设最终的答案是后缀 b ( b ≠ a ) b(b\ne a) b(b=a) 的一个前缀,那么 b > a b>a b>a ,否则 b b b 才是应当被找到的后缀。

如果这个前缀的长度超过 l c p ( a , b ) lcp(a,b) lcp(a,b) ,那么根据上面的结论有 a < b ′ a<b' a<b ,显然不成立(答案是不超过 a a a 的);所以其长度不超过 l c p ( a , b ) lcp(a,b) lcp(a,b) 。你惊讶地发现:它也是 a a a 的一个前缀

所以答案一定是 a a a 的前缀。二分即可,越长的前缀字典序越大。还是利用上面的不等式进行判断。

复杂度 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn) ,用 S A \tt SA SA 即可。

代码

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long int_;
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MaxN = 200010;

int sa[MaxN], rnk[MaxN<<1];
int tmp[MaxN<<1], buck[MaxN];
void prepare(int n,int m){
	memset(buck,0,m<<2);
	for(int i=0; i<n; ++i)
		++ buck[rnk[i]];
	for(int i=1; i<m; ++i)
		buck[i] += buck[i-1];
}
# define XEZ(x) sa[-- buck[rnk[x]]] = x
/** @brief from index 0 to @p n-1 */
void getSA(int a[],int n){
	memset(tmp+n,-1,n<<2); // EOF
	memset(rnk+n,-1,n<<2); // EOF
	memcpy(tmp,a,n<<2);
	sort(tmp,tmp+n); // to hash
	int m = unique(tmp,tmp+n)-tmp;
	for(int i=0; i<n; ++i)
		rnk[i] = lower_bound(tmp,
			tmp+m,a[i])-tmp;
	prepare(n,m);
	for(int i=0; i<n; ++i) XEZ(i);
	for(int w=1,p; m!=n; w<<=1,m=p+1){
		for(int i=n-w+(p=0); i<n; ++i)
			tmp[p ++] = i; // index
		for(int i=0; i<n; ++i)
			if(sa[i] >= w)
				tmp[p ++] = sa[i]-w;
		prepare(n,m);
		for(int i=n-1; ~i; --i)
			XEZ(tmp[i]); // big first
		memcpy(tmp,rnk,n<<2);
		rnk[sa[0]] = p = 0;
		for(int i=1; i<n; ++i){
			if(tmp[sa[i]] != tmp[sa[i-1]]||
			tmp[sa[i]+w] != tmp[sa[i-1]+w])
				++ p; // brand new one
			rnk[sa[i]] = p;
		}
	}
}

int heit[MaxN]; // height
void getHei(int a[],int n){
	for(int i=0,j,k=0; i<n; ++i)
		if(rnk[i] == 0)
			heit[0] = k = 0;
		else{
			if(k) -- k;
			j = sa[rnk[i]-1];
			while(max(i,j)+k <= n
			&& a[j+k] == a[i+k])
				++ k; // k = len
			heit[rnk[i]] = k;
		}
}

namespace RMQ{
	int st[20][MaxN], *item;
	int xez[MaxN]; // log2
	void prepare(int a[],int n){
		xez[0] = -1;
		for(int i=0; i<n; ++i)
			st[0][i] = i;
		for(int i=1; i<=n; ++i)
			xez[i] = xez[i>>1]+1;
		item = a; // copy it
		for(int j=0; (2<<j)<=n; ++j)
		for(int i=0; i+(2<<j)<=n; ++i){
			st[j+1][i] = st[j][i+(1<<j)];
			if(a[st[j+1][i]] > a[st[j][i]])
				st[j+1][i] = st[j][i];
		}
	}
	int query(int l,int r){
		int k = xez[r-l+1];
		r = st[k][r-(1<<k)+1];
		if(item[r] < item[st[k][l]])
			return r; // minimum
		return st[k][l];
	}
}

char wyl[MaxN]; int xyx[MaxN];

int main(){
//	freopen("4310\\2.in","r",stdin);
//	freopen("xez.out","w",stdout);
	int k = readint();
	scanf("%s",wyl);
	int n = strlen(wyl);
	for(int i=0; i<n; ++i)
		xyx[i] = wyl[i];
	getSA(xyx,n), getHei(xyx,n);
	RMQ::prepare(heit,n);
	int L = 0, R = n-1; // ErFen
	for(int mid=(L+R)>>1; L!=R; mid=(L+R)>>1){
		int cnt = 0, now = n; // cut time
		for(int i=0; i<n&&cnt<k; ++i){
			if(rnk[i] > mid){
				int x = RMQ::query(mid+1,rnk[i]);
				if(heit[x] == 0) cnt = k;
				now = min(now,i+heit[x]-1);
			}
			if(i == now) // it's a must
				++ cnt, now = n;
		}
		if(now != n) ++ cnt;
		if(cnt < k) // it's fine
			R = mid; // it can be smaller
		else L = mid+1; // not OK
	}
	int id = sa[L]; // which suf
//	printf("id = %d, suf = ",id);
//	puts(wyl+id);
	L = heit[rnk[id]]+1, R = n-id; // length
//	printf("BiSearch %d %d\n",L,R);
	for(int mid=(L+R)>>1; L!=R; mid=(L+R)>>1){
		int cnt = 0, now = n;
		for(int i=0; i<n&&cnt<k; ++i){
			if(rnk[i] > rnk[id]){
				int x = RMQ::query(rnk[id]+1,rnk[i]);
				x = min(heit[x],mid);
				if(x == 0) cnt = k;
				now = min(now,i+x-1);
			}
			else if(i == id)
				now = min(now,i+mid-1);
			if(i == now) ++ cnt, now = n;
		}
		if(now != n) ++ cnt;
		if(cnt < k) R = mid;
		else L = mid+1; // cannot
	}
	for(int i=id; i<id+L; ++i)
		putchar(wyl[i]);
	putchar('\n');
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值