解题报告 P2839 [国家集训队]middle

P2839 [国家集训队]middle

绝世好题

首先对于求区间 \([l,r]\) 的中位数,有一个套路可以套:

二分一个值 \(d\) ,每次将区间内 \(<d\) 的点设为 \(-1\) ,将 \(\ge d\) 的点设为 \(1\)。当区间和 \(\ge 0\) 时 \(d\) 值过大或刚好,若 \(<0\) 则 \(d\) 值过小。

我们继续观察题目。

我们发现,每次询问的区间不固定,但是 \([b+1,c-1]\) 这个区间是必须选择的,所以我们可以先求这个区间的和。

由于我们要最大化中位数,所以我们可以在 \([a,b]\) 中选取最大后缀,在 \([c,d]\) 中选取最大前缀。

将三个区间的和加起来,就是我们要求的和了。

如果我们暴力统计查询,单次查询 \(O(nlog\ n)\) 显然过不了,所以要想办法优化。

我们可以对每一个 \(d\) 建立一棵线段树同时维护区间和、最大前缀和、最大后缀和,下标维护元素位置。将 \(<d\) 的点设为 \(-1\) ,将 \(\ge d\) 的点设为 \(1\),就可以区间查询了。

但是这样干空间会炸,我们还要另想办法。

将序列元素排序我们发现,对两个相邻的 \(d\) 值,其构造出来的 " \(1,-1\) " 序列只有一处不同。换句话说,它们的线段树只有一条路径不同。

我们可以考虑使用主席树维护,将 \(d\) 所在位置的值改成 \(-1\) 即可,版本号依据 \(d\) 确定。

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

const int N=2e5+10;

struct node
{
	int lson,rson;
	int sum,lmax,rmax;//和,前缀,后缀
} tree[N*60];

int root[N],tot=0,q[10];

int n,MAX;
int arr[N],v[N];
vector<int> pos[N];

#define lnode tree[node].lson
#define rnode tree[node].rson
#define DEFMID int mid=(start+end)>>1

void push_up(int node)
{
	tree[node].sum=tree[lnode].sum+tree[rnode].sum;//和
	tree[node].lmax=max(tree[lnode].lmax,tree[lnode].sum+tree[rnode].lmax);//前缀和
	tree[node].rmax=max(tree[rnode].rmax,tree[rnode].sum+tree[lnode].rmax);//后缀和
}

int build(int start,int end)
{
	int node=++tot;
	if(start==end)
	{
		tree[node].sum=tree[node].lmax=tree[node].rmax=1;
		return node;
	}
	DEFMID;
	lnode=build(start,mid);
	rnode=build(mid+1,end);
	push_up(node);
	return node;
}

#define lnode1 tree[node1].lson
#define rnode1 tree[node1].rson

int update(int node,int start,int end,int val)
{
	int node1=++tot;
	tree[node1]=tree[node];
	if(start==end)
	{
		tree[node1].lmax=tree[node1].rmax=tree[node1].sum=-1;
		return node1;
	}
	DEFMID;
	if(val<=mid) lnode1=update(lnode,start,mid,val);
	else rnode1=update(rnode,mid+1,end,val);
	push_up(node1);
	return node1;
}

int query1(int node,int start,int end,int l,int r)
{
	if(r<l) return 0;
	if(l<=start&&end<=r) return tree[node].sum;
	DEFMID;
	if(r<=mid) return query1(lnode,start,mid,l,r);
	else if(l>mid) return query1(rnode,mid+1,end,l,r);
	else return query1(lnode,start,mid,l,r)+query1(rnode,mid+1,end,l,r);
}

int query2(int node,int start,int end,int l,int r)//最大前缀和;
{
	if(l<=start&&end<=r) return tree[node].lmax;
	DEFMID;
	if(r<=mid) return query2(lnode,start,mid,l,r);
	else if(l>mid) return query2(rnode,mid+1,end,l,r);
	else
	{
		int t1=query2(lnode,start,mid,l,mid);
		int t2=query1(lnode,start,mid,l,mid)+query2(rnode,mid+1,end,mid+1,r);
		return max(t1,t2);
	}
}

int query3(int node,int start,int end,int l,int r)
{
	if(l<=start&&end<=r) return tree[node].rmax;
	DEFMID;
	if(r<=mid) return query3(lnode,start,mid,l,r);
	else if(l>mid) return query3(rnode,mid+1,end,l,r);
	else
	{
		int t1=query3(rnode,mid+1,end,mid+1,r);
		int t2=query1(rnode,mid+1,end,mid+1,r)+query3(lnode,start,mid,l,mid);
		return max(t2,t1);
	}
}

bool check(int mid)
{
	int a=query1(root[mid],1,n,q[2]+1,q[3]-1);//求和
	int c=query2(root[mid],1,n,q[3],q[4]);//最大前缀和
	int b=query3(root[mid],1,n,q[1],q[2]);//最大后缀和
	return a+b+c>=0;
}

int solve()
{
	int l=1,r=MAX;
	while(l<r)
	{
		int mid=(l+r+1)>>1;
		if(check(mid)) l=mid;
		else r=mid-1;
	}
	return l;
}

int main()
{
	scanf("%d",&n);
	root[0]=build(1,n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&arr[i]);
		v[i]=arr[i];
	}
	sort(v+1,v+1+n);
	MAX=unique(v+1,v+1+n)-v-1;
	for(int i=1;i<=n;i++)
	{
		arr[i]=lower_bound(v+1,v+1+n,arr[i])-v;
		pos[arr[i]].push_back(i);
	}
	for(int i=1;i<=MAX;i++)//构建主席树
	{
		root[i]=root[i-1];
		for(int j=0;j<pos[i-1].size();j++)
			root[i]=update(root[i],1,n,pos[i-1][j]);//插入
	}
	int m,ans=0;
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		for(int t=1;t<=4;t++) scanf("%d",q+t);
		for(int t=1;t<=4;t++) q[t]=(q[t]+ans)%n+1;//我们的下标从1开始
		sort(q+1,q+5);
		ans=v[solve()];
		printf("%d\n",ans);
	}
	return 0;
}

上一篇:C/C++题 【含答案和解析】


下一篇:图片与文字对齐