数据结构——对顶堆

对顶堆

由一个大根堆和一个小根堆组成,小根堆里面的数永远比大根堆里面的数要大

用途:用于动态维护区间内第k大的数,要比线段树和动态平衡树写起来更简单

 比如说我们要维护第k大的数,那么我们肯定是将前k大的数放进小根堆,小根堆的堆顶就是第k大的数

我们来看一下维护第k大的数的代码

1.建立大根堆和小根堆

priority_queue<int> qmax;  //相当于大顶堆
priority_queue<int,vector<int>,greater<int>> qmin;  //相当于小顶堆

2.如果小根堆是空的,或者说当前元素大于小根堆的堆顶元素,那么就将当前元素存入小根堆,否则就存入大根堆 

		if(qmin.empty()||a>=qmin.top())
		{
			qmin.push(a);
		}
		else
		{
			qmax.push(a);
		}

3.大、小根堆的维护,当我们小根堆的元素,不满k个,就将大根堆的堆顶元素弹入小根堆,如果我们当前小根堆的元素超过了k个,就将小根堆的堆顶元素摊入大根堆 

		while(qmin.size()>k)
		{
			qmax.push(qmin.top());
			qmin.pop();
		}
		while(qmin.size()<k&&!qmax.empty())
		{
			qmin.push(qmax.top());
			qmax.pop();
		}

4.查询第k大的元素,访问小根堆的堆顶元素

cout<<qmin.top()<<"\n";

 例题

P1168 中位数

思路:去求奇数项的中位数,这个中位数的下标是在不断变化的,很板的一种动态维护第k大的元素的板题,直接套用对顶堆代码即可

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,k;
int a;
priority_queue<int> qmax;
priority_queue<int,vector<int>,greater<int> > qmin;
signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a;
		if(i%2==1)
		{
			k=(i+1)/2;
		}
		if(qmin.empty()||a>=qmin.top())
		{
			qmin.push(a);
		}
		else
		{
			qmax.push(a);
		}
		while(qmin.size()>k)
		{
			qmax.push(qmin.top());
			qmin.pop();
		}
		while(qmin.size()<k&&!qmax.empty())
		{
			qmin.push(qmax.top());
			qmax.pop();
		}
		if(i%2==1)
		{
			cout<<qmin.top()<<"\n";
		}
	}
	return 0;
}

 P7072 [CSP-J2020] 直播获奖

 思路:第一眼看上去,获奖分数线为多少?即i*w/100位置上的人的分数是多少,翻译过来还是第k大的元素是多少,动态维护第k大的对顶堆板题

直接写就行

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,k;
double w;
int a;
priority_queue<int>qmax;
priority_queue<int,vector<int>,greater<int> >qmin;
signed main()
{
	cin>>n>>w;
	for(int i=1;i<=n;i++)
	{
		cin>>a; 
		int z=floor(i*w/100);
		k=max(1LL,z);
		if(qmin.empty()||a>=qmin.top())
		{
			qmin.push(a);
		}
		else
		{
			qmax.push(a);
		}
		while(qmin.size()<k&&!qmax.empty())
		{
			qmin.push(qmax.top());
			qmax.pop();
		}
		while(qmin.size()>k)
		{
			qmax.push(qmin.top());
			qmin.pop();
		}
		cout<<qmin.top()<<" ";
	}
	return 0;
} 

P2085 最小函数值

 思路:我们发现a,b,c都是正数,那么其堆成周-b/2*a,永远都在负数的位置,也就是说,这n个函数,在0~正无穷内都是单调递增的,那么我们可以考虑一种剪枝的O(n*m)的写法,我们首先创建一个大顶堆,存储前k小的元素,我们在最外层遍历n个函数,内层遍历m个数,当n为1的时候,先将m个函数值存进去,然后当后续的函数,如果存在比堆顶元素小的,就存进大顶堆,弹出堆顶

,如果比堆顶元素大,那就立刻结束当前内层循环,因为后面也会比堆顶元素大,。

直到双重循环遍历完,然后输出倒序前m个即可

//P2085 最小函数值
#include<bits/stdc++.h>
using namespace std;
#define int long long

int n,m;
priority_queue<int>qmax;
int a,b,c;
int f;
int ans[200005];
signed main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>a>>b>>c;
		for(int j=1;j<=m;j++)
		{
			f=a*j*j+b*j+c;
			if(i==1)
			{
				qmax.push(f);
			}
			else
			{
				if(f<qmax.top())
				{
					qmax.pop();
					qmax.push(f);
				}
				else
				{
					break;
				}
			}
		}
	}
	for(int i=1;i<=m;i++)
	{
		ans[i]=qmax.top();
		qmax.pop();
	}
	for(int i=m;i>=1;i--)
	{
		cout<<ans[i]<<" ";
	}
	return 0;
}

P1631 序列合并

思路:和上面那个一个思路,我们写一个有剪枝的O(n^2)的写法 ,和上面那个思路差不多

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int a[100005];
int b[100005];
int ans[100005];
priority_queue<int> q;
signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	cin>>a[i];
	for(int i=1;i<=n;i++)
	cin>>b[i];
	sort(a+1,a+1+n);
	sort(b+1,b+1+n);
	int k;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			k=a[i]+b[j];
			if(i==1)
			{
				q.push(k);
			}
			else
			{
				if(k<q.top())
				{
					q.pop();
					q.push(k);
				}
				else
				{
					break;
				}
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		ans[i]+=q.top();
		q.pop();
	}
	for(int i=n;i>=1;i--)
	cout<<ans[i]<<" ";
	return 0;
}

P1801 黑匣子

思路:我们很容易发现,题目还是动态求第k小的,因此我们要将前k小的,放入大根堆中,当我们大根堆位空,或者说当前元素小于大根堆的堆顶,就存进来,我们用vis[i]去统计要进行输出的位置即可

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int a[200005];
int b[200005];
int vis[200005];
priority_queue<int> qmax;
priority_queue<int,vector<int>,greater<int> > qmin;

signed main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	for(int i=1;i<=m;i++)
	{
		cin>>b[i];
		vis[b[i]]++;
	}
	int k=0;
	for(int i=1;i<=n;i++)
	{
		if(qmax.empty()||a[i]<qmax.top())
        {
        	qmax.push(a[i]);
		}
        else
        {
        	qmin.push(a[i]);
		}
		while(vis[i])
		{
			k++;
			while(qmax.size()<k)
			{
				qmax.push(qmin.top());
				qmin.pop();
			}
			while(qmax.size()>k)
			{
				qmin.push(qmax.top());
				qmax.pop();
			}
			cout<<qmax.top()<<"\n";
			vis[i]--;
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值