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;
}