Running Median 题解
一.题目大意
给你P组数据,先输出数据编号和(n+1)/2,再依次输出所有前i(i为奇数)个数的中位数,每10个数换一行
二.题解
求中位数,作为一枚偏爱权值线段树的菜鸡,于是,我直接码了一棵权值线段树,对于每个有询问的i,我们直接查询kth((i+1)/2)即可。
不过,由于题目给的数字范围比较大,我们使用权值线段树前得先离散化一下
不过既然是题解,当然不能写的这么简单
我们来讲讲如何利用权值线段树实现kth查询吧。。。
我们知道,权值线段树一个节点维护的是这个节点所管的区间中值在这个区间的数的个数。
所以,如果,我们假设我们当前节点管的区间为(l,r),而我们要求第k小的数,那么,我们就有如下讨论:
1.l==r
这个区间的数只有l/r了,我们的答案就是l/r了
2.我们利用线段树的性质二分
我们知道,左儿子的区间和右儿子的区间正好相当于进行了二分,所以,我们判断下,第k小数在不在左儿子,如果在,就继续递归左儿子,否则递归右儿子
那么,我们如何判断第k大的点是否在左儿子呢?
假设左儿子有x个数,也就是说,左儿子包含了排序后第1->x小的数
那么,如果x>=k,则可以说明,第k小的数一定在左儿子,我们递归找左儿子的第k小的数即可
如果x<k呢?那么第k小的数一定在右儿子,那么,我们要递归找右儿子的第k小数嘛?
不对,我们应该找右儿子的第k-x小数,为啥呢?
因为,我们递归右儿子的时候,舍弃了左儿子的所有点,而左儿子的所有点都比我们要找的数小,所以,比我们要找的第k小的数 小的数少了x个,现在就只有k-x个了,所以我们应该找右儿子的第k-x小数~
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1001;
int a[N];
struct node{
int w,l,r;
}t[N*N];
int W[N<<2];bool laz[N<<2];
int L[N];
inline bool kkk(node x,node y){
return x.w!=y.w?x.w<y.w:x.l<y.l;
}
inline void down(int now){
if(laz[now]){
W[now<<1]=W[now<<1|1]=laz[now]=0;
laz[now<<1]=laz[now<<1|1]=1;
}
}
inline void insert(int now,int l,int r,int x){
down(now);
++W[now];
if(l==r){
return;
}
int mid=(l+r)>>1;
if(x<=mid){
insert(now<<1,l,mid,x);
}else{
insert(now<<1|1,mid+1,r,x);
}
}
inline int find(int now,int l,int r,int lc,int rc){
down(now);
if(lc<=l&&r<=rc){
return W[now];
}
int mid=(l+r)>>1,res=0;
if(lc<=mid){
res+=find(now<<1,l,mid,lc,rc);
}
if(rc>mid){
res+=find(now<<1|1,mid+1,r,lc,rc);
}
return res;
}
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
int e=0;
for(int i=1;i<=n;++i){
int val=0;
for(int j=i;j<=n;++j){
val^=a[j];
t[++e]=(node){val,i,j};
}
}
sort(t+1,t+e+1,kkk);
L[1]=1;int ans=0;
insert(1,1,n,t[1].r);
for(int i=2;i<=e;++i){
L[i]=i;
if(t[i].w==t[i-1].w){
L[i]=L[i-1];
//查询L[i]-i中r值小于t[i].l的个数
if(t[i].l>1){
ans+=find(1,1,n,1,t[i].l-1);
}
}else{
laz[1]=1;W[1]=0;
}
insert(1,1,n,t[i].r);
}
printf("%d",ans);
return 0;
}