bzoj2653 Middle 二分&主席树

    这题厉害啊。。。根本想不到。(据说是clj的题?)

    首先要想到二分答案,想到就解出一半了。假设有一个值x,如果x是区间[l,r]的中位数,且l∈[a,b],r∈[c,d],那么答案一定>=x;否则答案一定<x。

    那么如何判断x是否是[l,r]的中位数呢?实际上,如果使用二分的话,我们不需要知道x是否一定是[l,r]的中位数,只要知道[l,r]中位数是否>=x即可。我们首先看一下如何判断任意区间[u,v]的中位数是否>=x。

    对于所有的数,如果我们将>=x的数令为1,<x的数令为-1,将这个新的数列称为基于x的新数列。那么如果在某一个区间[u,v]中,新的数列的和>=0,那么[u,v]的中位数一定>=x。那么,如果我们要知道是否存在[l,r],且l∈[a,b],r∈[c,d],[l,r]的中位数>=x,只需要知道是否存在[l,r],l∈[a,b],r∈[c,d],[l,r]的新的数列的和>=0即可。换句话说,我们只需要知道l∈[a,b],r∈[c,d]时[l,r]的最大子段和。这个最大子段和显然可以用线段树维护,只需要得到任意区间[u,v]的左连续最大子段和,右连续最大子段和,以及区间和即可。这样,答案相当于[a,b]的右连续最大子段和+[c,d]的左连续最大子段和+[b+1,c-1]的区间和。

    所以关键是如何构造出这样的线段树,因为x的数量太多了,为O(N),因此不可能构建N颗基于x的新的数列的线段树。但是关键的是这些线段树的大小形态是完全相同的,所以可以用主席树的方式做。讲到这差不多应该知道怎么做了吧。

    首先,这里线段树是以a[i]的值构造的,因此我们首先将a[i]排序。首先构造最小的a[i]的线段树,我们构造一颗满的线段树。显然所有的数都>=a[i],因此将基于a[i]的新的数列全部赋为1构造线段树。

    现在假设我们在构造第k小的a[x],而且第k-1小的是a[y]。注意到相对于基于a[y]的,在基于a[x]的新的数列中,只有第y个数从1变成了-1。但是万一a[y]=a[x]怎么办?没关系你会发现询问的结果是一样的。因此我们只需要将第y个数变成-1,因为只变了1个数,所以只有logN个节点发生了变化,其余节点一个指针过去就行了。

    那么查询就很简单了。只需要判断x对应的线段树[a,b]的右连续最大子段和+[c,d]的左连续最大子段和+[b+1,c-1]的区间和是否>=0即可。

AC代码如下(还是比较简单的才2k):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
#define M 8000005
using namespace std;

struct node{ int x,y,sum; }a[N],val[M];
int n,m,trtot,rt[N],ls[M],rs[M];
void maintain(int k){
	int l=ls[k],r=rs[k]; val[k].sum=val[l].sum+val[r].sum;
	val[k].x=max(val[l].x,val[l].sum+val[r].x);
	val[k].y=max(val[r].y,val[l].y+val[r].sum);
}
void build(int &k,int l,int r){
	k=++trtot; if (l==r){ val[k].x=val[k].y=val[k].sum=1; return; }
	int mid=(l+r)>>1; build(ls[k],l,mid); build(rs[k],mid+1,r);
	maintain(k);
}
void ins(int l,int r,int x,int &y,int k,int v){
	y=++trtot; int mid=(l+r)>>1;
	if (l==r){ val[y].x=val[y].y=val[y].sum=v; return; }
	if (k<=mid){ rs[y]=rs[x]; ins(l,mid,ls[x],ls[y],k,v); }
	else{ ls[y]=ls[x]; ins(mid+1,r,rs[x],rs[y],k,v); }
	maintain(y);
}
node qry(int k,int l,int r,int x,int y){
	if (l==x && r==y) return val[k]; int mid=(l+r)>>1;
	if (y<=mid) return qry(ls[k],l,mid,x,y); else
	if (x>mid) return qry(rs[k],mid+1,r,x,y); else{
		node t1=qry(ls[k],l,mid,x,mid),t2=qry(rs[k],mid+1,r,mid+1,y);
		t1.x=max(t1.x,t1.sum+t2.x); t1.y=max(t2.y,t1.y+t2.sum);
		t1.sum+=t2.sum; return t1;
	}
}
bool cmp(node aa,node bb){ return aa.x<bb.x; }
bool ok(int x,int l1,int l2,int r1,int r2){
	int tmp=0,k=rt[x]; if (l2+1<r1) tmp=qry(k,1,n,l2+1,r1-1).sum;
	return tmp+qry(k,1,n,l1,l2).y+qry(k,1,n,r1,r2).x>=0;
}
int main(){
	scanf("%d",&n); int i;
	for (i=1; i<=n; i++){ scanf("%d",&a[i].x); a[i].y=i; }
	sort(a+1,a+n+1,cmp); build(rt[1],1,n);
	for (i=2; i<=n; i++) ins(1,n,rt[i-1],rt[i],a[i-1].y,-1);
	scanf("%d",&m); int ans=0,ask[4],q[4];
	while (m--){
		for (i=0; i<4; i++){ scanf("%d",&ask[i]); ask[i]=(ask[i]+ans)%n; }
		sort(ask,ask+4); for (i=0; i<4; i++) q[i]=ask[i]+1;
		int l=1,r=n+1; while (l+1<r){ int mid=(l+r)>>1; if (ok(mid,q[0],q[1],q[2],q[3])) l=mid; else r=mid; }
		printf("%d\n",ans=a[l].x);
	}
	return 0;
}

by lych

2016.2.12



你可能感兴趣的:(线段树,二分,主席树)