BZOJ 2653 middle
AC通道:http://www.lydsy.com/JudgeOnline/problem.php?id=2653
题目大意:多组询问,求左右端点在规定范围内移动所能得到的最大中位数。
[分析]
求中位数这类题目比较少见[笔者见识少...],但是它的性质比较优美——从小到大排序排在中间的数字。
如果往常求一个无序数列的中位数,最好的复杂度就是n*logn[快排一遍的复杂度],[因为你要知道每个元素在序列中的大小]
但是我们这里是多组询问,而且元素不会修改,也就是每个元素的相对大小会在一开始预处理就搞定。
现在我们再来想:左右端点只是一个范围,怎么求出满足情况的最优区间呢?
根据时间的效率,肯定不能用枚举。
那么这个区间就一定有性质,什么性质呢?中位数最大。
我们要从中位数最大来找到这个区间。
也就是这个区间里比中位数大的元素和比中位数小的元素一样多。
可想而知:我们希望这个区间内的大的数越多,小的数越少越好,可是大小只是一个相对的概念,必须要和中位数比较才知道什么叫大,什么叫小。
谁是最大的中位数呢?求的就是这个,我们怎么知道...那我们只好二分了。
二分出这个中位数之后,我们要找这个最优区间就好办了:
假设比中位数小的数值记作-1,比中位数大的数值记作1。
最优区间一定满足:区间元素[指的是1,-1这些数]的和最大。
求最大和的这个过程可以用很多数据结构解决了,即求[a,b]的右端最大+(b,c)的和+[c,d]的左端最大。
可是我其实只需要这个值大于0,当前二分的值就是合法的了,我就会去追求一个更大的中位数,对吧。
也就是说,思考的过程是:
二分一个答案->找到最优的区间->区间如果满足->去追寻更好的答案。
其中最优区间是找寻答案的前提,但只有先暂时确定一个较好值才能找到符合这个的最优区间。[逻辑上有点成环]
最后因为对于每个二分出来的中位数值都希望有一个[-1,1]的序列专门分给它,这个靠什么呢?
想起之前的可持久化线段树就好办了:
树的节点意义是下标的位置范围,树的节点同时需要记录下这个节点代表区间的“右端最大”、“左端最大”、“求和”三种属性
最小的元素所有位置都标上1[因为所有元素都比它大嘛...],
然后从小到大的添加元素,先复制上一棵树,每次将树上上一个元素所在的位置标上-1,同时需要在过程中更新出右端最大、左端最大、求和三种属性。
因为上一棵树和这一棵树的唯一区别就是上一棵树在上一个元素的位置上标记为1,而这一棵树标记为-1,这样每次就是更新树上的一条链,变化不会很大,采用的就是可持久化线段树的思想了。
最后献上代码:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; inline int in(){ int x=0;char ch=getchar(); while(ch>'9' || ch<'0') ch=getchar(); while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); return x; } const int maxn=20010; int n,m,key,cnt,ans; int a[maxn]; int id[maxn],rt[maxn]; struct Node{ int l,r,sz; int lx,rx,sm; }s[maxn*20]; bool cmp(const int &x1,const int &x2){ return a[x1]<a[x2]; } void renew(int x){ s[x].sm=s[s[x].l].sm+s[s[x].r].sm; s[x].lx=max(s[s[x].l].lx,s[s[x].l].sm+s[s[x].r].lx); s[x].rx=max(s[s[x].r].rx,s[s[x].r].sm+s[s[x].l].rx); } void build(int l,int r,int &rt){ if(l==r){ rt=++cnt,s[rt].sm=s[rt].lx=s[rt].rx=1; return ; } rt=++cnt; int mid=(l+r)>>1; build(l,mid,s[rt].l); build(mid+1,r,s[rt].r); renew(rt); } void update(int last,int l,int r,int &rt,int val){ rt=++cnt;s[rt]=s[last]; if(l==r){ s[rt].lx=s[rt].rx=s[rt].sm=val; return ; } int mid=(l+r)>>1; if(key<=mid) update(s[last].l,l,mid,s[rt].l,val); else update(s[last].r,mid+1,r,s[rt].r,val); renew(rt); } int get_all(int rt,int l,int r,int x,int y){ if(l==x && r==y) return s[rt].sm; int mid=(l+r)>>1; if(y<=mid) return get_all(s[rt].l,l,mid,x,y); else if(x>mid) return get_all(s[rt].r,mid+1,r,x,y); else return get_all(s[rt].l,l,mid,x,mid)+get_all(s[rt].r,mid+1,r,mid+1,y); } int get_lx(int rt,int l,int r,int x,int y){ if(l==x && r==y) return s[rt].lx; int mid=(l+r)>>1; if(y<=mid) return get_lx(s[rt].l,l,mid,x,y); else if(x>mid) return get_lx(s[rt].r,mid+1,r,x,y); else return max(get_lx(s[rt].l,l,mid,x,mid),get_all(s[rt].l,l,mid,x,mid)+get_lx(s[rt].r,mid+1,r,mid+1,y)); } int get_rx(int rt,int l,int r,int x,int y){ if(l==x && r==y) return s[rt].rx; int mid=(l+r)>>1; if(y<=mid) return get_rx(s[rt].l,l,mid,x,y); else if(x>mid) return get_rx(s[rt].r,mid+1,r,x,y); else return max(get_rx(s[rt].r,mid+1,r,mid+1,y),get_all(s[rt].r,mid+1,r,mid+1,y)+get_rx(s[rt].l,l,mid,x,mid)); } bool check(int k,int a,int b,int c,int d){ int sum=0; if(c>b+1) sum+=get_all(rt[k],0,n-1,b+1,c-1); sum+=get_rx(rt[k],0,n-1,a,b); sum+=get_lx(rt[k],0,n-1,c,d); return sum>=0; } int main(){ #ifndef ONLINE_JUDGE freopen("2653.in","r",stdin); freopen("2653.out","w",stdout); #endif n=in(); for(int i=0;i<n;i++) a[i]=in(),id[i]=i; sort(id,id+n,cmp); build(0,n-1,rt[0]); for(int i=1;i<n;i++) key=id[i-1],update(rt[i-1],0,n-1,rt[i],-1); int ord[4]; m=in(); while(m--){ ord[0]=in(),ord[1]=in(),ord[2]=in(),ord[3]=in(); for(int i=0;i<4;i++) ord[i]=(ord[i]+ans)%n; sort(ord,ord+4); int l=0,r=n,mid; while(l+1<r){ mid=(l+r)>>1; if(check(mid,ord[0],ord[1],ord[2],ord[3])) l=mid; else r=mid; } ans=a[id[l]]; printf("%d\n",ans); } return 0; }