[TJOI2010] 中位数
题目
Description
给定一个由 NN 个元素组成的整数序列,现在有两种操作:
- 1 add a1 add a:在该序列的最后添加一个整数 aa,组成长度为 N+1N+1 的整数序列。
- 2 mid2 mid:输出当前序列的中位数。
中位数是指将一个序列按照从小到大排序后处在中间位置的数。(若序列长度为偶数,则指处在中间位置的两个数中较小的那个)
例 11:[1,2,13,14,15,16][1,2,13,14,15,16] 中位数为 1313。
例 22:[1,3,5,7,10,11,17][1,3,5,7,10,11,17] 中位数为 77。
例 33:[1,1,1,2,3][1,1,1,2,3] 中位数为 11。
Input
第一行为初始序列长度 NN。第二行为 NN 个整数,表示整数序列,数字之间用空格分隔。第三行为操作数 MM,即要进行 MM 次操作。下面为 MM 行,每行输入格式如题意所述。
Output
对于每个 midmid 操作输出中位数的值。
Sample Input
6 1 2 13 14 15 16 5 add 5 add 3 mid add 20 mid
Sample Output
5 13
思路
查找中位数;
一共有四种方法,平衡树,二分+树状数组,权值线段树,堆;
因为树状数组和线段树写法需要离散化,要存下所有加入的数;
作者太懒,所以只介绍平衡树和堆的写法;
平衡树就是板子所以就不细说了;
堆的写法就是维护一个大根堆和一个小根堆;
查询第k个数时,大根堆表示前k减一个数;
那么答案就是小根堆的堆顶元素;
维护过程只需保证大根堆中所有元素都比小根堆小即可;
因为每次询问的都是中位数,
所以插入时,若插入前元素个数为偶数,则插入大根堆;
若插入前元素个数为奇数,则插入小根堆;
代码
平衡树写法
#include<bits/stdc++.h> typedef long long ll; using namespace std; const ll _=2e5+1; ll n,m; ll root,tot; ll size[_]; struct node { ll l,r,v,f; }a[_]; inline void update(ll p) { size[p]=size[a[p].l]+size[a[p].r]+1; } inline ll build(ll v) { a[++tot].v=v; a[tot].f=rand(); size[tot]=1; return tot; } void split(ll p,ll k,ll &x,ll &y) { if(p==0) { x=y=0; return; } if(a[p].v<=k) { x=p; split(a[p].r,k,a[p].r,y); } else { y=p; split(a[p].l,k,x,a[p].l); } update(p); } ll merge(ll x,ll y) { if(x==0||y==0) return x+y; if(a[x].f<a[y].f) { a[x].r=merge(a[x].r,y); update(x); return x; } else { a[y].l=merge(x,a[y].l); update(y); return y; } } void insert(ll v) { ll x,y; split(root,v-1,x,y); root=merge(merge(x,build(v)),y); } ll findk(ll k) { ll p=root; while(1) { if(k==size[a[p].l]+1) return a[p].v; if(k<size[a[p].l]+1) p=a[p].l; if(k>size[a[p].l]+1) { k-=size[a[p].l]+1; p=a[p].r; } } } int main() { scanf("%lld",&n); for(ll i=1;i<=n;i++) { ll x; scanf("%lld",&x); insert(x); } scanf("%lld",&m); for(ll i=1;i<=m;i++) { string opt; cin>>opt; if(opt=="add") { ll x; scanf("%lld",&x); n++; insert(x); } else printf("%lld\n",findk((n+1)/2)); } return 0; }
堆写法
#include<bits/stdc++.h> typedef long long ll; using namespace std; const ll _=2e5+1; ll n,m; ll a[_]; priority_queue<ll> q,qq;//q表示小根堆,qq为大根堆 int main() { scanf("%lld",&n); for(ll i=1;i<=n;i++) scanf("%lld",&a[i]); sort(a+1,a+n+1); for(ll i=1;i<n/2;i++) qq.push(a[i]); for(ll i=n/2;i<=n;i++) q.push(-a[i]);//大根堆里插入这个数的相反数就可以实现小根堆 scanf("%lld",&m); for(ll i=1;i<=m;i++) { string opt; cin>>opt; if(opt=="add") { ll x; scanf("%lld",&x); if(n%2==0) qq.push(x); else q.push(-x);//插入 n++; } else { while(!q.empty()&&!qq.empty()&&-q.top()<qq.top()) {//维护保证大根堆中每一个元素都比小根堆要小 ll x=-q.top(),xx=qq.top(); q.pop(); qq.pop(); q.push(-xx); qq.push(x); } printf("%lld\n",-q.top()); } } return 0; }