【主席树】2012集训队互测 Middle

来源:互联网 发布:数据分析师主要做什么 编辑:程序博客网 时间:2024/05/22 15:03

JZOJ 2902 集训队互测2012 middle(陈立杰)


【题目大意】给定一个序列,每次询问左端点在[a,b]中,右端点在[c,d]中的子序列中,最大的中位数。强制在线。


【题解】

这题虽然不是这几天做的,但是最近在搞数据结构,再总结一下还是会有收获的。(复习时应该看看)


设一个序列S从大到小排序后为S[1..k],则中位数为M=S[k/2向上取整]。

那就意味着,在序列S中,大于等于M的数大于等于k/2。如果把序列S转化为T,T[i]=1(S[i]>=M),-1(S[i]<M),则ΣT[i]一定非负。


这样我们很容易想到一个算法:二分答案+主席树

1.先按位置建一棵叶子节点权值为1的原始线段树(第一个版本的线段树)。要维护区间和sum,区间左侧最大前缀和lsum,和右侧最大前缀和rsum。

2.然后按权值从小到大的顺序依次在相应的位置修改,每次新建一个版本的线段树(可持久化)

假设当前到第i个版本的线段树,则要把第i-1大的数的位置权值修改为-1。这样在第i个版本的线段树上,比第i大的数还大(或等于)的数的位置上值为1,否则为-1。

3.二分答案。

二分到k时,就在第k个版本的线段树上查询。(程序实现时root[0]为第一个版本线段树的根)。

若Query(root[k-1],1,N,ask[1],ask[2]).rsum + Query(root[k-1],1,N,ask[2]+1,ask[3]-1).sum + Query(root[k-1],1,N,ask[3],ask[4]).lsum >= 0,就说明k可以为中位数,继续二分。


#include<cstdio>#include<cstring>#include<algorithm>#include<cstdlib>#include<cmath>#define fo(i,a,b) for (int i = a;i <= b;i ++)#define fd(i,a,b) for (int i = a;i >= b;i --)using namespace std;const int maxn = 2000000;struct node{int lsum,rsum,sum,l,r;node(){}node(int a,int b,int c) {lsum = a,rsum = b,sum = c,l = r = 0;}};int root[maxn];node tree[maxn];int a[maxn],b[maxn],pos[maxn];int N,Q,TOT,n,Last;int ask[10];bool cmp(int x,int y) { return a[x] < a[y]; }void Readin(){scanf("%d",&N);fo(i,1,N) scanf("%d",&a[i]),pos[i] = i;}void Discretize(){ sort(pos+1,pos+N+1,cmp); }void Update(int z){int lson = tree[z].l;int rson = tree[z].r;tree[z].lsum = max(tree[lson].sum + tree[rson].lsum,tree[lson].lsum);tree[z].rsum = max(tree[rson].sum + tree[lson].rsum,tree[rson].rsum);tree[z].sum = tree[lson].sum + tree[rson].sum;}void AddPoint(int &z,int x,int l,int r,int p){z = ++ TOT;if (l == r) {tree[z].l = tree[z].r = 0;tree[z].lsum = tree[z].rsum = -1;tree[z].sum = -1;} else {int Mid = (l + r) >> 1;if (p <= Mid) {AddPoint(tree[z].l,tree[x].l,l,Mid,p);tree[z].r = tree[x].r;} else {AddPoint(tree[z].r,tree[x].r,Mid+1,r,p);tree[z].l = tree[x].l;}Update(z);}}void Filltree(int &z,int l,int r){z = ++ TOT;if (l == r){tree[z].l = tree[z].r = 0;tree[z].lsum = tree[z].rsum = 1;tree[z].sum = 1;} else {int Mid = (l + r) >> 1;Filltree(tree[z].l,l,Mid);Filltree(tree[z].r,Mid+1,r);Update(z);}}void Maketree(){TOT = 0;Filltree(root[0],1,N);fo(i,1,N-1) AddPoint(root[i],root[i-1],1,N,pos[i]);}void Initialize(){Readin();Discretize();Maketree();}node Query(int z,int l,int r,int x,int y){if (y < x) return node(0,0,0);if (l == x && r == y) return tree[z];int Mid = (l + r) >> 1;if (y <= Mid) return Query(tree[z].l,l,Mid,x,y);else if (x > Mid) return Query(tree[z].r,Mid+1,r,x,y);else {node ret,retl,retr;retl = Query(tree[z].l,l,Mid,x,Mid);retr = Query(tree[z].r,Mid+1,r,Mid+1,y);ret.lsum = max(retl.sum + retr.lsum,retl.lsum);ret.rsum = max(retr.sum + retl.rsum,retr.rsum);ret.sum = retl.sum + retr.sum;return ret;}}bool Check(int k){return Query(root[k-1],1,N,ask[1],ask[2]).rsum + Query(root[k-1],1,N,ask[2]+1,ask[3]-1).sum + Query(root[k-1],1,N,ask[3],ask[4]).lsum >= 0;}void Work(){int L,R,Mid;L = 1;R = N;while (L < R){Mid = (L + R + 1) >> 1;if (Check(Mid)) L = Mid;else R = Mid - 1;}Last = a[pos[L]];printf("%d\n",a[pos[L]]);}int main(void){Initialize();scanf("%d",&Q);Last = 0;while (Q){fo(i,1,4) scanf("%d",&ask[i]);fo(i,1,4) ask[i] = (ask[i] + Last) % N + 1;sort(ask+1,ask+5);Work();Q --;}return 0;}


0 0
原创粉丝点击