luogu P3380 【模板】二逼平衡树(分块实现)

题目描述

您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:

  1. 查询 k k k 在区间内的排名
  2. 查询区间内排名为 k k k 的值
  3. 修改某一位值上的数值
  4. 查询 k k k 在区间内的前驱(前驱定义为严格小于 x x x,且最大的数,若不存在输出 − 2147483647 -2147483647 2147483647)
  5. 查询 k k k 在区间内的后继(后继定义为严格大于 x x x,且最小的数,若不存在输出 2147483647 2147483647 2147483647)

Solution

使用分块通过此题。

我们把一个长度为 N N N 的序列分成 N \sqrt N N 块,每块大小为 N \sqrt N N ,然后维护每个块的有序性。

对于操作 1,直接遍历区间内的块,统计答案即可。时间复杂度 O ( N ) O(\sqrt N) O(N )

对于操作 2,二分答案,用操作 1 检验答案;时间复杂度 O ( N log ⁡ N ) O(\sqrt N\log N) O(N logN)

对于操作 3,暴力修改,排序维护块内顺序,时间复杂度 O ( log ⁡ N ) O(\log \sqrt N) O(logN )

对于操作 4、5,等价于查询该数排名 + 1 / − 1 +1/-1 +1/1 的数,转化为操作 2。


这样我们便通过了此题。时间复杂度 O ( N 3 2 log ⁡ N ) O(N^{\frac 32}\log N) O(N23logN),空间复杂度 T ( N ) T(N) T(N)

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>


using namespace std;

const int MAXN=50010;
const int MAXE=230;
const int MAXA=(int)1e8;

int n,m,sq;
int sx,sy,sd;

struct point{
	int x,y;
	friend bool operator<(const point a,const point b){
		return a.y<b.y;
	}
}a[MAXN];
struct split{
	int l,r;
	int mininum;
	point e[MAXE];
	void update(){
		sort(e+1,e+(r-l+1)+1);
		mininum=e[1].y;
	}
	void build(point p[MAXN],int L,int R){
		if(R>n) R=n;
		l=L;r=R;
		for(int i=l;i<=r;++i)
			e[i-l+1]=p[i];
		update();
	}
	int query(int x){
		int left=1,right=(r-l+1),mid,sum=0;
		while(left<=right){
			mid=(left+right)/2;
			if(e[mid].y<x){
				sum=mid;
				left=mid+1;
			}else
				right=mid-1;
		}
		return sum;
	}
	int que(int left,int right,int x){
		int cnt=0;
		for(int i=l;i<=r;++i)
			if(e[i-l+1].x>=max(l,left)&&e[i-l+1].x<=min(r,right)&&e[i-l+1].y<x) ++cnt;
		return cnt;
	}
	int check(int left,int right,int x){
		if(left<=l&&r<=right)
			return mininum==x;
		for(int i=max(l,left);i<=min(r,right);++i)
			if(e[i-l+1].y==x) return 1;
		return 0;
	}
	void change(int x,int d){
		for(int i=1;i<=r-l+1;++i)
			if(e[i].x==x){
				e[i].y=d;
				break;
			}
		update();
	}
}s[MAXE];
int len=0;

int getnum(int x){
	return (x-1)/sq+1;
}
int rank(int l,int r,int x){
	int L=getnum(l),R=getnum(r);
	int cnt=0;
	for(int i=L+1;i<=R-1;++i)
		cnt+=s[i].query(x);
	cnt+=s[L].que(l,r,x);
	if(L!=R) cnt+=s[R].que(l,r,x);
	if(!cnt){
		for(int i=L;i<=R;++i)
			if(s[i].check(l,r,x))
				return cnt+1;
		return 1;
	}
	return cnt+1;
}
int find(int l,int r,int x){
	int left=0,right=MAXA,mid,s=0;
	while(left<=right){
		mid=(left+right)/2;
		if(rank(l,r,mid)<=x){
			s=mid;
			left=mid+1;
		}else
			right=mid-1;
	}
	return s;
}
void change(int x,int d){
	s[getnum(x)].change(x,d);
}
int findupper(int l,int r,int x){
	int rk=rank(l,r,x);
	if(rk>r-l+1) return 2147483647;
	bool flag=(find(l,r,rk)==x);
	return find(l,r,rk+flag);
}
int findlower(int l,int r,int x){
	int rk=rank(l,r,x);
	if(rk<=1) return -2147483647;
	return find(l,r,rk-1);
}
inline int read(){
	int x=0; char c;
	do c=getchar(); while(c<'0'||c>'9');
	while(c>='0'&&c<='9')
		x=x*10+c-48,c=getchar();
	return x;
}
int main(){
	n=read();m=read();sq=sqrt(n);
	for(int i=1;i<=n;++i)
		a[i]=(point){i,read()};
	int cnt=0;
	while(cnt<n){
		s[++len].build(a,cnt+1,cnt+sq);
		cnt+=sq;
	}
	for(int i=1;i<=m;++i){
		sd=read();
		switch(sd){
			case 1:
				sx=read();sy=read();sd=read();
				int c;
				printf("%d\n",c=rank(sx,sy,sd));
				break;
			case 2:
				sx=read();sy=read();sd=read();
				printf("%d\n",find(sx,sy,sd));
				break;
			case 3:
				sx=read();sd=read();
				change(sx,sd);
				break;
			case 4:
				sx=read();sy=read();sd=read();
				printf("%d\n",findlower(sx,sy,sd));
				break;
			case 5:
				sx=read();sy=read();sd=read();
				printf("%d\n",findupper(sx,sy,sd));
				break;
			default:
				break;
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值