P2839 [国家集训队]middle 主席树+求中位数技巧

首先做这个题 我们要用到一个常用的技巧   二分求中位数  

具体步骤就是  先二分一个值  在数组中把大于等于这个值的数都设为1  把小于这个值的数都设为-1 

如果数组和>=0  那么 l=mid+1

否则的话  r=mid-1

那么我们如何快速的进行这个操作呢   

假如一个询问是 a,b,c,d  a<b<c<d   并且对于 区间a~d我们都进行了上述的赋值操作

那么 b+1~c-1之间的值是肯定要算进去的   

然后我们需要在 a~b区间内找个最大后缀和  在 c~d区间内找个最大前缀和  就可以完成了 

可是如何快速进行这样的+1和-1赋值   

首先我们知道中位数肯定是数组中的某个数  容易想到的是 我们把数组的数按照升序排序   在二分下标  最后得到中位数  而这个下标其实可以对应一棵 1~i 的前缀主席树了 

主席树维护 和,最大前缀和,最大后缀和   当i=1的时候  区间所有的数都是大于等于它的   所以初始化为 r-l+1 (也就是每个位置都对应1) 当i=2时 因为i=1对应的值小于它  所以我们把它对应的位置-置为-1   后面的树也进行类似的操作 

那么当我们二分到一个mid值时 我们就到对应的树上面去查   

首先  加上  b+1~c-1的和  其次 加上 a~b的最大后缀  最后加上 c~d的最大前缀  如果总和>=0 则 l=mid+1 否则 r=mid-1

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int rt[N],id[N],val[N],tot,q[4],n;
inline int in(){
	int w=0,x=0;char c=0;
	while(c<'0'||c>'9') w|=c=='-',c=getchar();
	while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return w?-x:x;
}
struct Mx{
	int lmax,rmax,sum;
	void init(){
		lmax=rmax=-N,sum=0;
	}
}ans;
struct hjt{
	int ls,rs;
	Mx v;
}T[N*30];
bool cmp(int a,int b){
	return val[a]<val[b];
}
Mx merge(Mx A,Mx B){
	Mx C;
	C.lmax = max(A.lmax, A.sum + B.lmax);
	C.rmax = max(B.rmax, A.rmax + B.sum);
	C.sum = A.sum + B.sum;
	return C;
}
void build(int &o,int l,int r){
	o=++tot;
	T[o].v.lmax=T[o].v.rmax=T[o].v.sum=r-l+1;
	if(l==r) return;
	int mid = l+r>>1;
	build(T[o].ls,l,mid);
	build(T[o].rs,mid+1,r);
}
void upd(int &o,int l,int r,int pos,int v){
	T[++tot]=T[o];
	o=tot;
	if(l==r){
		T[o].v.lmax=T[o].v.rmax=T[o].v.sum=v;
		return;
	}
	int mid = l+r>>1;
	if(pos<=mid) upd(T[o].ls,l,mid,pos,v);
	else upd(T[o].rs,mid+1,r,pos,v);
	T[o].v = merge(T[T[o].ls].v, T[T[o].rs].v);
}
void query(int o,int l,int r,int L,int R){
	if(L<=l&&R>=r){
		ans=merge(ans,T[o].v);
		return;
	}
	int mid = l+r>>1;
	if(L<=mid) query(T[o].ls,l,mid,L,R);
	if(R>mid) query(T[o].rs,mid+1,r,L,R);
}
bool check(int mid){
	//printf("mid=%d\n",mid);
	int val=0;
	if(q[1]+1<=q[2]-1) ans.init(),query(rt[mid],1,n,q[1]+1,q[2]-1),val+=ans.sum;
	ans.init();query(rt[mid],1,n,q[0],q[1]),val+=ans.rmax;
	ans.init();query(rt[mid],1,n,q[2],q[3]);val+=ans.lmax;
	return val>=0;
}
int main(){
	n=in();
	build(rt[1],1,n);
	T[0].v.init();
	for(int i = 1; i <= n; i++)
		val[i]=in(),id[i]=i;
	sort(id+1,id+1+n,cmp);
	for(int i = 2; i <= n; i++){
		rt[i]=rt[i-1];
		//printf("i=%d\n",i);
		upd(rt[i],1,n,id[i-1],-1);
	}
	int m,las=0;
	m=in();
	for(int i = 1; i <= m; i++){
		for(int i = 0; i < 4; i++)
			q[i]=(in()+las)%n+1;
		sort(q,q+4);
		int l=1,r=n;
		while(l<=r){
			int mid = l+r>>1; 
			if(check(mid)) las=val[id[mid]],l=mid+1;
			else r=mid-1;
		}
		printf("%d\n",las);
	}
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值