[BZOJ 2653] middle
Link:
Solution:
针对中位数问题的特殊处理:
假设中位数为$x$,那么把小于$x$的赋值−1,把大于等于$x$的赋值+1
然后看看是否有连续的一段$sum\ge 0$,如有则保证能取到这样的$x$
如果$sum$大于0则证明答案应该更大,相反答案应该更小
$sum$显然是单调的,所以满足二分性质,考虑二分答案。
接下来考虑如何维护区间和,
线段树维护三个值$sum$,$lsum$,$rsum$分别表示区间和,从左端/右端起最大连续子序列和
满足条件$[a,b][c,d]$的最长连续子序列和即为$sum(b,c)+rsum(a,b-1)+lsum(c+1,d)$
但明显不可能对所有的值都单独构建一棵线段树,于是我们想到了主席树
想要有可重用的部分,就要使得建树的顺序具有单调性:
于是我们将原数列排序,先将线段树上所有点都赋为1
然后让第$i$个棵树在第$i-1$棵树的基础上增加一条第$i-1$个点为-1的链即可
Code:
#include <bits/stdc++.h> using namespace std; #define NOW seg[cur] #define LC NOW.ls #define RC NOW.rs inline int read() { char ch;int num,f=0; while(!isdigit(ch=getchar())) f|=(ch=='-'); num=ch-'0'; while(isdigit(ch=getchar())) num=num*10+ch-'0'; return f?-num:num; } template<class T> inline void putnum(T x) { if(x<0)putchar('-'),x=-x; register short a[20]={},sz=0; while(x)a[sz++]=x%10,x/=10; if(sz==0)putchar('0'); for(int i=sz-1;i>=0;i--)putchar('0'+a[i]); putchar('\n'); } const int MAXN=1e6; struct FunTree { int ls,rs; int sum,lsum,rsum; }seg[MAXN]; int n,dat[MAXN],id[MAXN],root[MAXN],cnt=0; bool cmp(int x,int y) { return dat[x]<dat[y]; } void Update(int cur) { NOW.sum=seg[LC].sum+seg[RC].sum; NOW.lsum=max(seg[LC].lsum,seg[LC].sum+seg[RC].lsum); NOW.rsum=max(seg[RC].rsum,seg[RC].sum+seg[LC].rsum); } void Build_Tree(int& cur,int l,int r) { cur=++cnt; if(l==r){NOW.sum=NOW.lsum=NOW.rsum=1;return;} int mid=(l+r)/2; Build_Tree(LC,l,mid);Build_Tree(RC,mid+1,r); Update(cur); } void Insert(int pre,int& cur,int pos,int val,int l,int r) { cur=++cnt; if(l==r){NOW.sum=NOW.lsum=NOW.rsum=-1;return;} int mid=(l+r)>>1;NOW=seg[pre]; if(pos<=mid) Insert(seg[pre].ls,NOW.ls,pos,val,l,mid); else Insert(seg[pre].rs,NOW.rs,pos,val,mid+1,r); Update(cur); } int Query(int a,int b,int cur,int l,int r) { if(a<=l && r<=b) return NOW.sum; int mid=(l+r)>>1,ret=0; if(a<=mid) ret+=Query(a,b,NOW.ls,l,mid); if(b>mid) ret+=Query(a,b,NOW.rs,mid+1,r); return ret; } int Left(int a,int b,int cur,int l,int r) //注意求Left和Right时与求Sum的不同 { if(a<=l && r<=b) return NOW.lsum; int mid=(l+r)>>1; if(b<=mid) return Left(a,b,NOW.ls,l,mid); if(a>mid) return Left(a,b,NOW.rs,mid+1,r); return max(Left(a,mid,NOW.ls,l,mid),Query(a,mid,NOW.ls,l,mid)+Left(mid+1,b,NOW.rs,mid+1,r)); } int Right(int a,int b,int cur,int l,int r) { if(a<=l && r<=b) return NOW.rsum; int mid=(l+r)>>1; if(a>mid) return Right(a,b,NOW.rs,mid+1,r); if(b<=mid) return Right(a,b,NOW.ls,l,mid); return max(Right(mid+1,b,NOW.rs,mid+1,r),Query(mid+1,b,NOW.rs,mid+1,r)+Right(a,mid,NOW.ls,l,mid)); } bool check(int x,int a,int b,int c,int d) { int ret1=Query(b,c,root[x],1,n); int ret2=max(Left(c+1,d,root[x],1,n),0); int ret3=max(Right(a,b-1,root[x],1,n),0); return ret1+ret2+ret3>=0; } int main() { n=read(); for(int i=1;i<=n;i++) dat[i]=read(),id[i]=i; sort(id+1,id+n+1,cmp);sort(dat+1,dat+n+1); Build_Tree(root[1],1,n); for(int i=2;i<=n;i++) Insert(root[i-1],root[i],id[i-1],-1,1,n); int T=read(),last=0; while(T--) { int q[5]; for(int i=1;i<=4;i++) q[i]=(read()+last)%n+1; sort(q+1,q+5); int l=1,r=n; while(l<=r) //二分答案 { int mid=(l+r)>>1; if(check(mid,q[1],q[2],q[3],q[4])) l=mid+1; else r=mid-1; } putnum(last=dat[r]); } return 0; }
Review:
1、针对中位数的套路:
把小于$x$的赋值−1,把大于等于$x$的赋值+1,然后看看是否有连续的一段$Sum$>=0
2、主席树建树时要使其顺序具有一定单调性
3、注意对查找$Left$、$Right$时和查找$Sum$时的区别