堆顶技术学习指南

前置芝士

对顶堆

对顶堆是一种简单好用的动态维护单调区间第k大数或第k小数的数据结构。由一个大顶堆和一个小顶堆构成。

小顶堆

使用c++的STL内裤的 priority_queue<int,vector<int>,greater<int>> mi_hp,进行操作

单调区间第k大值

[problem description]

[支持的操作]

添加一个元素

询问第k大值(k会变)

不能查询特定的[l,r]区间的第小值

智能查询[1,now]的区间的第k小值

[solved]

priority_queue<int> mx_hp;//大顶堆
priority_queue<int,vector<int>,greater<int>> mi_hp;//小顶堆,存放最大的k个元素,以至于可以O(1)找到第k大的值。
int n,k,num;//n:元素个数,动态k,num:需要加入的数
void qwq(){
    //调整小顶堆里的元素个数
    if(mi_hp.size()<k){
        mi_hp.push(mx_hp.top());
        mx_hp.pop();
    }
    if(mi_hp.size()>k){
        mx_hp.push(mi_hp.top());
        mi_hp.pop();
    }
}
void push(int num){
    //如果新加入的元素大于mx_hp的堆顶的最大元素,可以直接加入小顶堆,
    //否则需要先将mx_hp的堆顶加入到mi_hp中,将num加入到大顶堆中
    //维护小顶堆一定是存放最大的k个数
    if(num>=mx_hp.top()) mi_hp.push(num);
    else mx_hp.push(num);
    qwq();
}
void solve(){
    cin>>n;
    mx_hp.push(0);//避免边界判断
    for(int i=1;i<=n;i++){
       	//k=pass
        cin>>num;
        push(num);
        cout<<mi_hp.top()<<" ";
    }
    cout<<endl;
}

单调区间第k小值

[problem description]

[支持添加元素]

查找小幅度变化的k值(第k小值)

不能查询特定的[l,r]区间的第小值

智能查询[1,now]的区间的第k小值

[solved]

const int N = 200010;
int a[N];
priority_queue<int> mx_hp;//大顶堆
priority_queue<int, vector<int>, greater<int>> mi_hp; //小顶堆,存放最大的k个元素,以至于可以O(1)找到第k大的值。
int n, k=1, num; //n:元素个数,动态k,num:需要加入的数
int m;
//调整堆元素个数
void qwq() {
    //调整小顶堆里的元素个数
    if (mx_hp.size() < k) {
        mx_hp.push(mi_hp.top());
        mi_hp.pop();
    }
    if (mx_hp.size() > k) {
        mi_hp.push(mx_hp.top());
        mx_hp.pop();
    }
}
//添加元素
void push(int num) {
    //如果新加入的元素小于mi_hp的堆顶的最小元素,可以直接加入大顶堆,
    //否则需要先将mi_hp的堆顶加入到mx_hp中,将num加入到小顶堆中
    //维护大顶堆一定是存放最小的k个数
    if (num <= mi_hp.top()) mx_hp.push(num);
    else mi_hp.push(num);
    qwq();
}
void solve() {
    cin >> n;
    mi_hp.push(dinf);//避免边界判断,存放不可能出现的最大值
    for (int i = 1; i <= n; i++) {
        //k=pass
        push(a[i]);
       //查询第k小值:mx_hp.top();
    }
}

动态维护中位数

[problem description]

给定一个长度为 \(N\) 的非负整数序列 \(A\),对于前奇数项求中位数。

[input]

第一行一个正整数 \(N\)

第二行 \(N\) 个正整数 \(A_{1\dots N}\)

[output]

\(\lfloor \frac{N + 1}2\rfloor\) 行,第 \(i\) 行为 \(A_{1\dots 2i - 1}\) 的中位数。

[sample]

in

7
1 3 5 7 9 11 6

out

1
3
5
6

\(1 \le N ≤ 100000\)\(0 \le A_i \le 10^9\)

[solved]

const int N=100010;
int n,a[N];
priority_queue<int> mx_hp;
priority_queue<int,vector<int>,greater<int>> mi_hp;
int k,num;//动态维护第k小节点
void qwq(){
	if(mx_hp.size()<k){
		mx_hp.push(mi_hp.top());
		mi_hp.pop();
	}
	if(mx_hp.size()>k){
		mi_hp.push(mx_hp.top());
		mx_hp.pop();
	}
}
void push(int num){
	if(num<=mi_hp.top()) mx_hp.push(num);
	else mi_hp.push(num);
	qwq();
}
void solve() {
    //freopen("a.in","r",stdin);
    //freopen("a.out","w",stdout); 
    mi_hp.push(inf);//避免边界判断,存放不可能出现的最大值
    cin>>n;
    for(int i=1;i<=n;i++){
    	cin>>a[i];
    }
    for(int i=1;i<=n;i++){
    	k=(1+i)/2;//动态维护k值
    	push(a[i]);
    	if(i&1){
    		cout<<mx_hp.top()<<endl;
    	}
    }
}

序列合并

[problem description]

有两个长度为 \(N\)单调不降序列 \(A,B\),在 \(A,B\) 中各取一个数相加可以得到 \(N^2\) 个和,求这 \(N^2\) 个和中最小的 \(N\) 个。

[input]

第一行一个正整数 \(N\)

第二行 \(N\) 个整数 \(A_{1\dots N}\)

第三行 \(N\) 个整数 \(B_{1\dots N}\)

[output]

一行 \(N\) 个整数,从小到大表示这 \(N\) 个最小的和。

[sample]

a.in

3
2 6 6
1 4 8

a.out

3 6 7

[datas]

\(1 \le N \le 10^5\)\(1 \le a_i,b_i \le 10^9\)

[solved]

(1)首先将a[1]+b[i](1<=i<=n)全部放入堆中

(2)再取最小值val,再将val-a[i]+a[i+1]放入堆中

const int N=100010;
int n;
int a[N],b[N];

priority_queue<pii,vector<pii>,greater<pii>> mi_hp;
void solve() {
    //freopen("a.in","r",stdin);
    //freopen("a.out","w",stdout); 
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++) cin>>b[i];
	for(int i=1;i<=n;i++) mi_hp.push({a[1]+b[i],1});
	for(int i=1;i<=n;i++){
		auto mi=mi_hp.top();
        mi_hp.pop();
		int idx=mi.se;
		int val=mi.fi;
		if(idx+1<=n)
		mi_hp.push({val-a[idx]+a[idx+1],idx+1});
		cout<<val<<" ";
	}
	cout<<endl;
}
posted @ 2023-10-12 11:17  White_Sheep  阅读(16)  评论(0编辑  收藏  举报