洛谷2839 BZOJ2653 middle 主席树 二分答案

题目链接
题意:
给你n个数,q次询问,每次给你4个数a、b、c、d,求左端点在区间 [ a , b ] [a,b] [a,b]内,右端点在区间 [ c , d ] [c,d] [c,d]之间的所有子区间的最大中位数。强制在线。

题解:
首先,我们假设答案中位数是 x x x,那么我们把区间 [ a , d ] [a,d] [a,d]内大于等于 x x x的数都变为 1 1 1,小于 x x x的数都变为 − 1 -1 1,那么我们只需要看区间 [ a , b ] [a,b] [a,b]的最大后缀和加区间 [ c , d ] [c,d] [c,d]的最大前缀和再加区间 [ b , c ] [b,c] [b,c]的和是否大于0即可( c > b c>b c>b,若 c = b c=b c=b,则不需要加第三个数)。而每一次我们可以开一个线段树来维护以上的三个量,用线段树而不是其他做法的原因是别的做法没有办法进一步优化。想到线段树之后我们考虑能不能不去每次重构,因为复杂度爆炸。那么我们就考虑来优化建树的过程。
我们可以把一开始的数组离散化并排序之后建权值线段树,这样
每次就只需要在上次树的基础上修改一条链得来。这种前一棵线段树结构和后一棵线段树之间的联系不难让我们想到要用主席树的建树方法。那么我们从小到大枚举离散化后的 x x x,一开始建树的时候所有数都满足大于等于数组中最小的数,所以一开始每一个位置都是1,之后每往后一个数,就相当于之前那些比它小的数都是 − 1 -1 1,也就是要在上一棵树的基础上把从根到上个数的那条链都变成-1就可以了。
这样我们就建好了主席树,而我们发现我们每次需要把每个数都枚举一遍来确定中位数是哪个数,我们发现如果一个数 x x x,它是合法的,那么比它小的数一定也存在一个区间使得区间和大于等于0,所以是可以二分的。那么我们对于每个询问二分答案,在主席树上查询维护的三个信息并判断和是否大于等于0即可。
为什么二分的答案一定是原数组中 的某个数?
因为如果区间外有个不是原数组中的数满足要求,那么原数组中一定会有个数判定的区间和大于它。(我是这么理解的)
代码:

#include <bits/stdc++.h>
using namespace std;

int n,q,le[800010],ri[800010],lsum[800010],rsum[800010],sum[800010],t[800010];
int cnt,lastans=0,c[5];
struct node
{
	int x,id;
}a[20010];
inline int cmp(node x,node y)
{
	return x.x<y.x;
}
inline void pushup(int x)
{
	sum[x]=sum[le[x]]+sum[ri[x]];
	lsum[x]=max(lsum[le[x]],sum[le[x]]+lsum[ri[x]]);
	rsum[x]=max(rsum[ri[x]],sum[ri[x]]+rsum[le[x]]);
}
inline int build(int l,int r)
{
	int rt=++cnt;
	if(l==r)
	{
		sum[rt]=1;
		lsum[rt]=1;
		rsum[rt]=1;
		return rt;
	}
	int mid=(l+r)>>1;
	le[rt]=build(l,mid);
	ri[rt]=build(mid+1,r);
	pushup(rt);
	return rt;
}
inline int update(int pre,int l,int r,int x)
{
	int rt=++cnt;
	le[rt]=le[pre];
	ri[rt]=ri[pre];
	if(l==r)
	{
		sum[rt]=-1;
		lsum[rt]=-1;
		rsum[rt]=-1;
		return rt;
	}
	int mid=(l+r)>>1;
	if(x<=mid)
	le[rt]=update(le[pre],l,mid,x);
	else
	ri[rt]=update(ri[pre],mid+1,r,x);
	pushup(rt);
	return rt;
}
inline int q1(int rt,int l,int r,int ll,int rr)
{
	if(ll==l&&rr==r)
	return sum[rt];
	int mid=(l+r)>>1;
	if(rr<=mid)
	return q1(le[rt],l,mid,ll,rr);
	else if(ll>mid)
	return q1(ri[rt],mid+1,r,ll,rr);
	else
	return q1(le[rt],l,mid,ll,mid)+q1(ri[rt],mid+1,r,mid+1,rr);
} 
inline int q2(int rt,int l,int r,int ll,int rr)
{
	if(l==ll&&r==rr)
	return rsum[rt];
	int mid=(l+r)>>1;
	if(rr<=mid)
	return q2(le[rt],l,mid,ll,rr);
	else if(ll>mid)
	return q2(ri[rt],mid+1,r,ll,rr);
	else
	return max(q2(ri[rt],mid+1,r,mid+1,rr),q1(ri[rt],mid+1,r,mid+1,rr)+q2(le[rt],l,mid,ll,mid));
}
inline int q3(int rt,int l,int r,int ll,int rr)
{
	if(l==ll&&r==rr)
	return lsum[rt];
	int mid=(l+r)>>1;
	if(rr<=mid)
	return q3(le[rt],l,mid,ll,rr);
	else if(ll>mid)
	return q3(ri[rt],mid+1,r,ll,rr);
	else
	return max(q3(le[rt],l,mid,ll,mid),q1(le[rt],l,mid,ll,mid)+q3(ri[rt],mid+1,r,mid+1,rr));
}
inline int check(int mid,int a,int b,int c,int d)
{
	int ans=0;
	if(c-1>b)
	ans+=q1(t[mid],1,n,b+1,c-1);
	ans+=q2(t[mid],1,n,a,b);
	ans+=q3(t[mid],1,n,c,d);
	if(ans>=0)
	return 1;
	else
	return 0;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&a[i].x);
		a[i].id=i;
	}
	sort(a+1,a+n+1,cmp);
	t[1]=build(1,n);	
	for(int i=2;i<=n;++i)
	t[i]=update(t[i-1],1,n,a[i-1].id);
	scanf("%d",&q);
	for(int i=1;i<=q;++i)
	{
		scanf("%d%d%d%d",&c[1],&c[2],&c[3],&c[4]);
		c[1]=(c[1]+lastans)%n+1;
		c[2]=(c[2]+lastans)%n+1;
		c[3]=(c[3]+lastans)%n+1;
		c[4]=(c[4]+lastans)%n+1;
		sort(c+1,c+5);
		int l=1,r=n,mid,ans;
		while(l<=r)
		{			
			mid=(l+r)>>1;
			if(check(mid,c[1],c[2],c[3],c[4]))
			{
				ans=mid;
				l=mid+1;
			}
			else
			r=mid-1;
		}
		lastans=a[ans].x;
		printf("%d\n",lastans);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值