middle区间最大中位数

题目链接

        求左端点在【a,b】,右端点在【c,d】上的最大中位数序列。考虑二分中位数。

        我们令小于等于中位数的数为-1,大于中位数的数为1,如果查的区间和大于等于0表示中位数不够大,还可以变大,因为比现在二分到的值大的数比较多。现在问题变成怎么判断区间和了。

        我们可以对每个数开一个线段树,记录一个区间内比该数小的数为1,我们去求这个区间和就是我们上面要找的区间和了。很显然,个每一个数开一个线段树的空间是不可行的,我们考虑动态开点的主席树来减少空间浪费。

        对于那个不可行的每个点开一个线段树,我们是要记录比当前小或等于的数的位置为-1,比当前位置大的数的位置为1,也就是说我们需要先将序列排序,然后从小到大去插入信息,且插入的是他们的原数组下标,因为我们需要查找的是一个原数组区间里比当前值小的数个数。如果我们先排序再按照有序数组去建立主席树,那么对于每个数我们只需要插入log条信息,且由于主席树可以继承前面状态的主席树,实际上我们就是给每个数开了一个我们原先需要的线段树了。并且我们是从小到大插入的,也就是说到当前的数为止,前面将数置为-1的位置都是比当前数小的,也就是说是完全满足我们一开始的需求的,对于当前状态的线段树比自己小的数的位置都是-1,比自己大的数都是1.

        对于二分,我们要去找第一次令区间和小于0的中位数,而不是令区间等于0的中位数。比如1234,我们要找3而不是2.二分的时候l是1,r是n+1,因为主席树的root【下标】就是排完序后的a数组的下标,mid为2就是a【2】(排完序的a数组),它对应的线段树是root【2】。因为change里面我们都是先将线段树直接继承上一个版本(直接赋值,儿子指向同一个地方),然后就改了一个位置为-1,其他都是和前面一样的,也就是说当前版本的线段树就是前缀和的结果了(和以前那些主席树不一样,不用减去之前的版本了,共用相同部分的线段树)。

        对于左区间和右区间就是去求最大区间和,中间那块直接求和。

        代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
typedef unsigned long long ull;
typedef pair<ll,ll> pii;
const int inf=0x3f3f3f3f;
const int N=2e4+10;
const int mod=1e9+7;
const ll INF=2e9+10;
#define mid ((l+r)>>1)

int n,Q,q[4],last;
struct A{
	int x,id;
	bool operator<(A &t){
		return x<t.x;
	}
}a[N];
int root[N],tot;
int ls[N*20],rs[N*20],sum[N*20],lmx[N*20],rmx[N*20];

void pushup(int u){
	sum[u]=sum[ls[u]]+sum[rs[u]];
	lmx[u]=max(lmx[ls[u]],lmx[rs[u]]+sum[ls[u]]);
	rmx[u]=max(rmx[rs[u]],rmx[ls[u]]+sum[rs[u]]);
}

void build(int &u,int l,int r){
	u=++tot;
	if(l==r){
		lmx[u]=rmx[u]=sum[u]=1;
		return;
	}
	build(ls[u],l,mid);
	build(rs[u],mid+1,r);
	pushup(u);
}

void change(int &u,int v,int l,int r,int p,int k){
	u=++tot;
	ls[u]=ls[v];rs[u]=rs[v];
	sum[u]=sum[v];lmx[u]=lmx[v];
	rmx[u]=rmx[v];
	if(l==r){
		lmx[u]=rmx[u]=sum[u]=k;
		return;
	}
	if(p<=mid) change(ls[u],ls[v],l,mid,p,k);
	else change(rs[u],rs[v],mid+1,r,p,k);
	pushup(u);
}

int qsum(int u,int l,int r,int x,int y){
	if(x<=l&&y>=r) return sum[u];
	int res=0;
	if(x<=mid) res+=qsum(ls[u],l,mid,x,y);
	if(y>mid) res+=qsum(rs[u],mid+1,r,x,y);
	return res;
}

int qlmx(int u,int l,int r,int x,int y){
	if(x<=l&&y>=r) return lmx[u];
	if(y<=mid) return qlmx(ls[u],l,mid,x,y);
	if(x>mid) return qlmx(rs[u],mid+1,r,x,y);
	return max(qlmx(ls[u],l,mid,x,y),
	qsum(ls[u],l,mid,x,y)+qlmx(rs[u],mid+1,r,x,y));
}

int qrmx(int u,int l,int r,int x,int y){
	if(x<=l&&y>=r) return rmx[u];
	if(y<=mid) return qrmx(ls[u],l,mid,x,y);
	if(x>mid) return qrmx(rs[u],mid+1,r,x,y);
	return max(qrmx(rs[u],mid+1,r,x,y),
	qsum(rs[u],mid+1,r,x,y)+qrmx(ls[u],l,mid,x,y));
}

bool check(int u,int a,int b,int c,int d){
	int s=0;
	if(c-b>=2) s+=qsum(root[u],1,n,b+1,c-1);
	s+=qrmx(root[u],1,n,a,b);
	s+=qlmx(root[u],1,n,c,d);
	return s>=0;
}

void solve(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i].x;
		a[i].id=i;
	}
	sort(a+1,a+1+n);
	build(root[0],1,n);
	for(int i=1;i<=n;i++)
		change(root[i],root[i-1],1,n,a[i].id,-1);
	cin>>Q;
	while(Q--){
		for(int i=0;i<4;i++) cin>>q[i];
		for(int i=0;i<4;i++) q[i]=(q[i]+last)%n+1;
		sort(q,q+4);
		int l=0,r=n+1;//二分的是中位数的下标,在主席树里第二小就是root【2】
		while(l<=r){
			check(mid,q[0],q[1],q[2],q[3])?l=mid+1:r=mid-1;
		} 
		last=a[l].x;
		cout<<last<<endl;
	} 
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int t=1;
	//cin>>t;
	while(t--){
		solve();
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值