对顶堆
定义
我们知道,
对顶角:
如果一个角的两边分别是另一个角两边的 反向延长线 ,且这两个角有公共顶点,那么这两个角是对顶角。
所以说对顶堆是如果堆的两边分别是另个堆两边的反向延长线 ,且这两个堆有公共顶点,那么这两个堆是对顶堆。
对顶堆:
对顶堆由一个大根堆与一个小根堆组成,
小根堆维护大值即前 \(k\) 大的值(包含第 \(k\) 个),大根堆维护小值即比第 \(k\) 大数小的其他数。
操作
对顶堆呢,其实算是对 堆 的一种应用,不能称得上是一种新的数据结构。
它可以解决的问题可以被抽象为:动态维护一个序列上第 \(k\) 大的数,\(k\) 值可能会发生变化。
总结:可以单点查询
这两个堆构成的数据结构支持以下操作:
-
维护:
当小根堆的大小小于 \(k\) 时,不断将大根堆堆顶元素取出并插入小根堆,直到小根堆的大小等于 \(k\);
当小根堆的大小大于 \(k\) 时,不断将小根堆堆顶元素取出并插入大根堆,直到小根堆的大小等于 \(k\);
-
插入元素:
若插入的元素大于等于小根堆堆顶元素,则将其插入小根堆,否则将其插入大根堆,然后维护对顶堆;
-
查询第 \(k\) 大元素:
小根堆堆顶元素即为所求;
-
删除第 \(k\) 大元素:
删除小根堆堆顶元素,然后维护对顶堆;
-
\(k\) 值 \(+1/-1\):
根据新的 \(k\) 值直接维护对顶堆。
复杂度
显然,查询第 \(k\) 大元素的时间复杂度是 \(O(1)\) 的。
由于插入、删除或更新 \(k\) 值后,小根堆的对顶的大小与 \(k\) 值相差最多为 \(1\) ,
故每次维护最多只需对大根堆与小根堆中的元素进行一次调整,因此,这些操作的时间复杂度都是 \(O(\log n)\) 的。
P1168 中位数
中位数
题目描述
给出一个长度为\(N\)的非负整数序列\(A_i\),对于所有\(1 ≤ k ≤ (N + 1) / 2\),输出\(A_1, A_1 \sim A_3, …,A_1 \sim A_{2k - 1}\)的中位.数。即前\(1,3,5,…\)个数的中位数。
输入格式
第\(1\)行为一个正整数\(N\),表示了序列长度。
第\(2\)行包含\(N\)个非负整数\(A_i (A_i ≤ 10^9)\)。输出格式
共\((N + 1) / 2\)行,第\(i\)行为\(A_1, A_3, …, A_{2k - 1}\)的中位数。
【完整代码】
#include<bits/stdc++.h>
using namespace std;
priority_queue<int>lq;
priority_queue<int,vector<int>,greater<int> >rq;
int n,x;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&x);
if(lq.size()==rq.size()){
if(lq.empty()||x<=rq.top()){
lq.push(x);
}
else{
int y=rq.top();
rq.pop();
lq.push(y);
rq.push(x);
}
}
else{
if(x>=lq.top()){
rq.push(x);
}
else{
int y=lq.top();
lq.pop();
rq.push(y);
lq.push(x);
}
}
if(i&1){
printf("%d\n",lq.top());
}
}
return 0;
}
例题
\(【luogu】-SP15376【RMID - Running Median】\)
\(【luogu】-SP16254【RMID2 - Running Median Again】\)
\(【luogu】-P1801【黑匣子】\)
\(【luogu】-P1168【中位数】\)
\(【luogu】-P3871【TJOI2010-中位数】\)