对顶堆

定义

我们知道,

对顶角:

如果一个角的两边分别是另一个角两边的 反向延长线 ,且这两个角有公共顶点,那么这两个角是对顶角。

所以说对顶堆是如果堆的两边分别是另个堆两边的反向延长线 ,且这两个堆有公共顶点,那么这两个堆是对顶堆。

对顶堆:

对顶堆由一个大根堆与一个小根堆组成,
小根堆维护大值即前 \(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 中位数

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-中位数】\)

posted @ 2022-08-07 21:39  Ciaxin  阅读(237)  评论(0编辑  收藏  举报