【bzoj2653: middle】二分答案+可持久化线段树

2653: middle

Time Limit: 20 Sec   Memory Limit: 512 MB
Submit: 2164   Solved: 1199
[ Submit][ Status][ Discuss]

Description

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

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





题目大意:给出一列数,有q个询问,每次询问给出a,b,c,d, 意思是在[a,b]中找到一个左端点l,[c,d]中找到一个右端点r,使得[l,r]区间里的中位数最大,输出最大的中位数。题目强制在线。

对于每一次询问:我们二分答案,设当前答案为x,进行验证。我们把大于等于x的位置的贡献记为1,小于x的位置的贡献记为-1,那么只要找到一个[l,r]区间满足sum(l,r)的值>=0 (ps. 这里说的中位数感觉应该要>=1吧,因为题目里说两个数列都是0开始,除法为向下取整,并且是从大到小排序,手算一下,sum(l,r)的值应该要>0的时候才是能满足的),那么这个答案就是满足题意的,继续找是否有更大的答案,同理验证。

那么问题就转为如何找到这样的一个(l,r)区间:

我们用一个线段树来记录每个位置的贡献为1还是-1(大于等于x的贡献为1,小于的贡献为-1),那么只要看满足题意的max(sum(l,r))是否大于等于0就可以了。因为l在[a,b],r在[c,d], 那么[b,c]这个区间里的值肯定是必须要计算的,记为find_sum(b,c),然后我们找到[a,b-1]这个区间里向右对齐的最大和,记为max(find_r(a,b-1),0),[c+1,d]中向左对齐的最大和,记为max(find_l(c+1,d),0) 。(这里要取max是因为可以一个都不取,所以最小的值是0)最后判断max(sum(l,r))+max(find_r(a,b-1),0)+max(find_l(c+1,d),0)>=0是否成立。

但是这样的话需要n棵线段树(每个不同的值为基础,贡献是不同的),我们考虑简化,我们可以用可持久化线段树。先将n个数排序,再插入可持久化线段树中,因为前一个数和当前这棵树只有一个位置是不同的,所以可以用可持久化线段树来简化问题。

这样就结束了。

#include
#include
#include
#include
#include
#define INF 1e9
#define ll long long
#define N 25005
#define M 20
using namespace std;
int tr[N*M],rt[N],L[N*M],R[N*M],sz,n,m,q[5],a[N],b[N],ls[N*M],rs[N*M],x,T;
mapmp;
void updata(int p){
	tr[p]=tr[ls[p]]+tr[rs[p]];
	L[p]=max(L[ls[p]],tr[ls[p]]+L[rs[p]]);
	R[p]=max(R[rs[p]],tr[rs[p]]+R[ls[p]]);
}
void build(int &p,int l,int r){
	if(!p) p=++sz;
	if(l==r){
		tr[p]=L[p]=R[p]=1;
		return ;
	}
	int mid=(l+r)/2;
	build(ls[p],l,mid);build(rs[p],mid+1,r);
	updata(p);
}
void insert(int p1,int &p,int l,int r,int x){
	if(!p) p=++sz;
	if(l>x||ry||x>r) return 0;
//	if(tr[p]==0) return 0;
	if(x<=l&&r<=y) return tr[p];
	int mid=(l+r)/2;
	return find_sum(ls[p],l,mid,x,y)+find_sum(rs[p],mid+1,r,x,y);
}
int find_l(int p,int l,int r,int x,int y){
	if(l>y||x>r) return 0;
	if(x<=l&&r<=y) return L[p];
	int mid=(l+r)/2;
	if(y<=mid) return find_l(ls[p],l,mid,x,y);
	else if(x>mid) return find_l(rs[p],mid+1,r,x,y);
	else return max(find_l(ls[p],l,mid,x,mid),find_sum(ls[p],l,mid,x,mid)+find_l(rs[p],mid+1,r,mid+1,y));
}
int find_r(int p,int l,int r,int x,int y){
	if(l>y||x>r) return 0;
	if(x<=l&&r<=y) return R[p]; 
	int mid=(l+r)/2;
	if(y<=mid) return find_r(ls[p],l,mid,x,y);
	else if (x>mid) return find_r(rs[p],mid+1,r,x,y);
	else return max(find_r(rs[p],mid+1,r,mid+1,y),find_sum(rs[p],mid+1,r,mid+1,y)+find_r(ls[p],l,mid,x,mid));
}
bool check(int x){
	int l1=q[0],r1=q[1],l2=q[2],r2=q[3];
	int tmp=find_sum(rt[x],1,n,r1,l2);
	if(l2!=r2)tmp+=max(0,find_l(rt[x],1,n,l2+1,r2));
	if(l1!=r1)tmp+=max(0,find_r(rt[x],1,n,l1,r1-1));
	return tmp>=0;
}
int main(){
	freopen("1.in","r",stdin);
	freopen("1.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]),b[i]=a[i],mp[a[i]]=i;
	sort(b+1,b+n+1);
	//for(int i=1;i<=n;i++)
	//	pos[mp[b[i]]]=i;
	build(rt[0],1,n);
	for(int i=1;i<=n;i++)
		insert(rt[i-1],rt[i],1,n,mp[b[i]]);
	x=0;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%d%d",&q[0],&q[1],&q[2],&q[3]);
		for(int i=0;i<4;i++)
			q[i]=(q[i]+x)%n;
		for(int i=0;i<4;i++) q[i]++;
		sort(q,q+4);
		int l=1,r=n,mid;
		while(l



你可能感兴趣的:(【bzoj2653: middle】二分答案+可持久化线段树)