luogu P2839 [国家集训队]middle

这题因为一些弱智错误调了好久。。。

考虑如何判断一个数是否是一堆数的中位数(不经过排序):记你想要判断的数为\(mid\),然后把 \(\geq mid\) 的数设为\(1\)\(\leq mid\) 的数设为\(-1\),然后给这些数求个和,若\(sum<0\),则 \(mid\) 比真正的中位数大,反之则比中位数小。

是不是发现了什么,可以二分找中位数!

回到这个题,我们发现每次给区间排序是爆炸的复杂度,所以只能通过上述非排序方法来找中位数,由于数列是不变的,所以我们可以预处理一个数据结构:序列每个点根据 \(mid\) 赋值为\(1\)\(-1\),要求支持维护区间求和,区间最大前缀,区间最大后缀(下面解释为什么)。

每次询问的区间让我们从\([a,b]\)之前选左端点,从\([c,d]\)之间选右端点,可以发现\((b,c)\)这段区间的值是必选的,所以先把他们累和。

然后我们发现如果最后答案可以比当前 \(mid\) 大,那么一定得满足你选的这段区间 \(sum>=0\),现在我们想让中位数最大,所以我们要尽量让我们选的区间的 \(sum\) 最大,我们分别把\([a,b]\)的最大后缀和\([c,d]\)的最大前缀算出来累加进去就好了。

现在的问题是如果我们对每一个 \(mid\) 建树空间复杂度为\(O(n^2)\),显然不行。但是我们又发现,\(mid\) 每次 \(+1\) 只会让那些值为 \(mid\) 的数从 \(1\) 变为 \(1\),均摊下来一共也就\(O(n)\)次变化,可以使用主席树来记录。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>

using namespace std;

const int N=100009;
int n,a[N],b[N],c[N],l[N],r[N],rt[N],chishi,Real[N],cnt,rev[N],m;
vector <int> v[N];
struct E
{
	int l,r;
	#define l(x) B[x].l
	#define r(x) B[x].r
}B[N*32];
struct Dot
{
	int sum,Suf,Pre;
	#define s(x) A[x].sum
	#define Suf(x) A[x].Suf
	#define Pre(x) A[x].Pre
	
	Dot operator + (const Dot &A)const
	{
		Dot B;
		B.sum=A.sum+sum;
		B.Pre=max(Pre,sum+A.Pre);
		B.Suf=max(A.Suf,A.sum+Suf);
		return B;
	} 
}A[N*32];

void push_up(int k)
{
	if(!l(k)||!r(k)) A[k]=A[l(k)+r(k)];
	else A[k]=A[l(k)]+A[r(k)];
}

Dot Query(int k,int l,int r,int x,int y);

void build(int &k,int l,int r)
{
	if(!k) k=++chishi;
	if(l==r)
	{
		s(k)=Suf(k)=Pre(k)=1;
		return;
	}
	int mid=l+r>>1;
	build(l(k),l,mid);
	build(r(k),mid+1,r);
	push_up(k);
}

void NewPoint(int &k,int last,int l,int r,int x,int y)
{
	k=++chishi,A[k]=A[last],B[k]=B[last];
	if(l==r)
	{
		s(k)=Suf(k)=Pre(k)=y;
		return;
	}
	int mid=l+r>>1;
	if(mid>=x)
		NewPoint(l(k),l(last),l,mid,x,y);
	else
		NewPoint(r(k),r(last),mid+1,r,x,y);
	push_up(k);
}

void init()
{
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]),b[i]=a[i];
	sort(b+1,b+1+n);
	m=unique(b+1,b+1+n)-(b+1);
	for (int i=1;i<=n;i++)
		c[i]=lower_bound(b+1,b+1+m,a[i])-b,rev[c[i]]=a[i];
	for (int i=1;i<=n;i++)
		v[c[i]].push_back(i);
	build(rt[1],1,n);
	Real[1]=cnt=1;
	for (int i=2;i<=m;i++)
	{
		for (int j=0;j<v[i-1].size();j++)
		{
			cnt++;
			NewPoint(rt[cnt],rt[cnt-1],1,n,v[i-1][j],-1);
		}
//		for (int j=1;j<=n-6;j++)
//			printf("%d ",Query(rt[cnt],1,n,j,j+6).Suf);puts("");
		Real[i]=cnt;
	}
}

Dot Query(int k,int l,int r,int x,int y)
{
	if(l>=x&&r<=y)
		return A[k];
	int mid=l+r>>1;
	if(mid>=x&&mid<y)
		return Query(l(k),l,mid,x,y)+Query(r(k),mid+1,r,x,y);
	if(mid>=x)
		return Query(l(k),l,mid,x,y);
	return Query(r(k),mid+1,r,x,y);
}

bool check(int mid,int a,int b,int c,int d)
{
	a++,b++,c++,d++;
	int Root=rt[Real[mid]];
	int res=b<c-1?Query(Root,1,n,b+1,c-1).sum:0;
	Dot Q1=Query(Root,1,n,a,b),Q2=Query(Root,1,n,c,d);
	return res+Q1.Suf+Q2.Pre>=0;
}

void work()
{
	int q[5],a,b,c,d,Q,last=0;
	scanf("%d",&Q);
	while(Q--)
	{
		scanf("%d %d %d %d",&a,&b,&c,&d);
		q[1]=(last+a)%n,q[2]=(last+b)%n,q[3]=(last+c)%n,q[4]=(last+d)%n;
		sort(q+1,q+1+4);
//		printf("%d %d %d %d\n",q[1],q[2],q[3],q[4]);
		int l=1,r=m,mid;
		while(l<=r)
		{
			mid=l+r>>1;
			if(check(mid,q[1],q[2],q[3],q[4]))
				l=mid+1;
			else
				r=mid-1;
		}
		printf("%d\n",last=rev[r]);
	}
}

int main()
{
	init();
	work();
	return 0;
}
posted @ 2020-06-16 06:39  With_penguin  阅读(66)  评论(0编辑  收藏  举报