【BZOJ2653】middle 二分+可持久化线段树

【BZOJ2653】middle

Description

一个长度为n的序列a,设其排过序之后为b,其中位数定义为b[n/2],其中a,b从0开始标号,除法取下整。给你一个
长度为n的序列s。回答Q个这样的询问:s的左端点在[a,b]之间,右端点在[c,d]之间的子序列中,最大的中位数。
其中a<b<c<d。位置也从0开始标号。我会使用一些方式强制你在线。

Input

第一行序列长度n。接下来n行按顺序给出a中的数。
接下来一行Q。然后Q行每行a,b,c,d,我们令上个询问的答案是
x(如果这是第一个询问则x=0)。
令数组q={(a+x)%n,(b+x)%n,(c+x)%n,(d+x)%n}。
将q从小到大排序之后,令真正的
要询问的a=q[0],b=q[1],c=q[2],d=q[3]。  
输入保证满足条件。
第一行所谓“排过序”指的是从大到小排序!

Output

Q行依次给出询问的答案。

Sample Input

5
170337785
271451044
22430280
969056313
206452321
3
3 1 0 2
2 3 1 4
3 1 4 0
271451044
271451044
969056313

Sample Output

HINT

0:n,Q<=100
1,...,5:n<=2000
0,...,19:n<=20000,Q<=25000

题解:好吧这题不看题解还真的很难想~

首先二分中位数还是挺好像的,但问题是怎么判断一个中位数是否可行。一个中位数mid可行的条件是序列中(≥mid的数的个数)≥(<mid的数的个数),也就是说,我们将比≥mid的数看成1,<mid的数看成-1,那么需要存在一段区间,使得区间和非负。这又和可持久化线段树有什么关系呢?

我们将所有数排序,然后令1-n的初值都是1,然后将n个数从小到大扔到可持久化线段树中去,并将对应位置变成-1,这样就很好的满足了所给条件。现在问题就是如何判断[a,b]-[c,d]中有没有符合条件的区间,只需要对线段树维护区间连续子段和,最大连续前缀子段和,最大后缀子段和,然后搞一搞就行了。(小白逛公园的简化)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=20010;
int n,m,maxx,minn,tot,ans;
struct sag
{
	int ls,rs,sum,lm,rm,sm;
}s[maxn*1000];
struct node
{
	int num,org;
}p[maxn];
int q[10],rt[maxn];
int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<'0'||gc>'9')	{if(gc=='-')f=-f;	gc=getchar();}
	while(gc>='0'&&gc<='9')	ret=ret*10+gc-'0',gc=getchar();
	return ret*f;
}
int max(int a,int b,int c)
{
	return max(max(a,b),c);
}
void pushup(int x)
{
	s[x].lm=max(s[s[x].ls].lm,s[s[x].ls].sum+s[s[x].rs].lm,0);
	s[x].rm=max(s[s[x].rs].rm,s[s[x].rs].sum+s[s[x].ls].rm,0);
	s[x].sm=max(s[s[x].ls].sm,s[s[x].rs].sm,s[s[x].ls].rm+s[s[x].rs].lm);
	s[x].sum=s[s[x].ls].sum+s[s[x].rs].sum;
}
void build(int l,int r,int &x)
{
	if(!x)	x=++tot;
	if(l==r)
	{
		s[x].sum=s[x].sm=s[x].lm=s[x].rm=1;
		return;
	}
	int mid=l+r>>1;
	build(l,mid,s[x].ls),build(mid+1,r,s[x].rs);
	pushup(x);
}
void insert(int x,int &y,int l,int r,int pos)
{
	if(r<pos)	return ;
	y=++tot;
	if(l==r)
	{
		s[y].sum=-1,s[y].lm=s[y].rm=s[y].sm=0;
		return ;
	}
	int mid=l+r>>1;
	if(pos<=mid)	s[y].rs=s[x].rs,insert(s[x].ls,s[y].ls,l,mid,pos);
	else	s[y].ls=s[x].ls,insert(s[x].rs,s[y].rs,mid+1,r,pos);
	pushup(y);
}
bool cmp(node a,node b)
{
	return a.num<b.num;
}
int qs(int l,int r,int x,int a,int b)
{
	if(a>b)	return 0;
	if(a<=l&&r<=b)	return s[x].sum;
	int mid=l+r>>1;
	if(b<=mid)	return qs(l,mid,s[x].ls,a,b);
	if(a>mid)	return qs(mid+1,r,s[x].rs,a,b);
	return qs(l,mid,s[x].ls,a,b)+qs(mid+1,r,s[x].rs,a,b);
}
int ql(int l,int r,int x,int a,int b)
{
	if(a>b)	return 0;
	if(a<=l&&r<=b)	return s[x].rm;
	int mid=l+r>>1;
	if(b<=mid)	return ql(l,mid,s[x].ls,a,b);
	if(a>mid)	return ql(mid+1,r,s[x].rs,a,b);
	return max(ql(l,mid,s[x].ls,a,b)+qs(mid+1,r,s[x].rs,a,b),ql(mid+1,r,s[x].rs,a,b));
}
int qr(int l,int r,int x,int a,int b)
{
	if(a>b)	return 0;
	if(a<=l&&r<=b)	return s[x].lm;
	int mid=l+r>>1;
	if(b<=mid)	return qr(l,mid,s[x].ls,a,b);
	if(a>mid)	return qr(mid+1,r,s[x].rs,a,b);
	return max(qr(mid+1,r,s[x].rs,a,b)+qs(l,mid,s[x].ls,a,b),qr(l,mid,s[x].ls,a,b));
}
int solve(int sta)
{
	int a=ql(1,n,rt[sta-1],q[0],q[1]-1);
	int b=qs(1,n,rt[sta-1],q[1],q[2]);
	int c=qr(1,n,rt[sta-1],q[2]+1,q[3]);
	if(a+b+c>=0)	return 1;
	return 0;
}
int main()
{
	n=rd();
	int i,j,l,r,mid;
	for(i=1;i<=n;i++)	p[i].num=rd(),p[i].org=i,maxx=max(maxx,p[i].num),minn=min(minn,p[i].num);
	build(1,n,rt[0]);
	sort(p+1,p+n+1,cmp);
	for(i=1;i<=n;i++)	insert(rt[i-1],rt[i],1,n,p[i].org);
	m=rd();
	for(i=1;i<=m;i++)
	{
		for(j=0;j<4;j++)	q[j]=(rd()+ans)%n+1;
		sort(q+0,q+4);
		l=1,r=n+1;
		while(l<r)
		{
			mid=l+r>>1;
			if(solve(mid))	l=mid+1;
			else	r=mid;
		}
		ans=p[l-1].num;
		printf("%d\n",ans);
	}
	return 0;
}

转载于:https://www.cnblogs.com/CQzhangyu/p/6895025.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值