算法学习入门---priority_queue(C++)

目录

1.priority_queue介绍

2.洛谷---【模板】堆

3.牛客---第k小

4.牛客---除2!

5.洛谷---最小函数值

6.洛谷---序列合并

7.洛谷---舞蹈课(难题)


1.priority_queue介绍

内置类型创建堆:

  • priority_queue在queue头文件中,除了具有了顺序排列特性以外,常用函数有pop、push、top、empty与size函数,函数用法与其他数据结构大致一样
  • priority_queue<int> heap; 表示创建出一个大根堆(默认为大根堆)
  • priority_queue<int,vector,less<int>> heap; 创建大根堆的完整格式
  • priority_queue<int,vector,greater<int>> heap; 创建小根堆的完整格式
  • 记忆方式:不同于sort函数中使用less、greater模板,堆的创建当中小根堆用大于(greater),大根堆用小于(less)

结构体类型创建堆:

要求:结构体中有a、b、c三个变量,要求根据b的大小来创建堆

方法:通过运算符重载(给运算符一个新定义)的方式来完成

代码1(根据b的大小建立起来的大根堆):

cpp 复制代码
#include<iostream>
#include<queue>
using namespace std;

typedef struct node{
	int a,b,c;
	
	bool operator < (const node& x)const
	{
		return b<x.b;
	}
}node;

int main()
{
	priority_queue<node> heap;
	for(int i=1;i<=5;i++)
	{
		heap.push({i-1,i+1,i-1});
	}
	while(heap.size())
	{
		node t = heap.top();heap.pop();
		cout<<t.a<<" "<<t.b<<" "<<t.c<<endl;
	}
	return 0;
}

代码2(根据b的大小建立起来的小根堆):

cpp 复制代码
typedef struct node{
	int a,b,c;
	
	bool operator < (const node& x)const
	{
		return b>x.b;
	}
}node;

2.洛谷---【模板】堆

代码:

cpp 复制代码
#include<iostream>
#include<queue>
using namespace std;

int main()
{
	priority_queue<int,vector<int>,greater<int>> heap;
	int n;cin>>n;
	while(n--)
	{
		int in;cin>>in;
		if(in==1)
		{
			int input;cin>>input;
			heap.push(input);
		}
		if(in==2) cout<<heap.top()<<endl;
		if(in==3) heap.pop();
	}
	return 0;
}

3.牛客---第k小

存放所有的数在堆中会比较麻烦,所以只存k个于k中并使用大根堆,那么头部的元素即为第k小的元素

代码:

cpp 复制代码
#include<iostream>
#include<queue>
using namespace std;

int main()
{
	priority_queue<int> heap;
	int n,m,k;cin>>n>>m>>k;
	while(n--)
	{
		int in;cin>>in;
		heap.push(in);
		if(heap.size()>k)heap.pop();
	}
	while(m--)
	{
		int in;cin>>in;
		if(in==1)
		{
			int input;cin>>input;
			heap.push(input);
			if(heap.size()>k)heap.pop();
		}
		if(in==2)
		{
			if(heap.size()==k)cout<<heap.top()<<endl;
			else cout<<-1<<endl;
		}
	}
}

4.牛客---除2!

代码:

cpp 复制代码
#include<iostream>
#include<queue>
using namespace std;
#define int long long

signed main()
{
	int n,k;cin>>n>>k;
	int ret=0;
	priority_queue<int> heap;
	while(n--)
	{
		int input;cin>>input;
		if(input%2)ret+=input;
		else heap.push(input);
	}
	while(heap.size()&&k--)
	{
		int cur = heap.top();heap.pop();
		cur/=2;
		if(cur%2) ret+=cur;
		else heap.push(cur);
	}
	while(heap.size())
	{
		int val = heap.top();heap.pop();
		ret+=val;
	}
	cout<<ret;
	return 0;
}

代码易错点:

当堆中没有元素时,k可能还不为0,所以在进行除2操作时还需注意,堆里有元素可以用来除2操作

5.洛谷---最小函数值

在所有函数的函数值当中,挑选出最小的m个(如有重复的函数值,则有几个算几个)

如下图所示,每个不同函数因为x不同,值也会跟着变

由于函数值全都可以保证是单调递增的,所以 x == 1 中函数值最小的,到了 x == 2 时也只会是所有函数值中最小的,所以可以先对x为1时函数值最小的函数计算x为2时的情况,如果还是比x==1时除自身以外的任意一个函数值小,那么继续计算其x==3的情况,这就可以用到小根堆的特性来保存函数值;给每个函数一个编号,每个函数值对应一个x,所以可以搞出一个结构体,其中有<函数值,函数编号,函数值对应的x>;然后根据函数值创建出一个小根堆,该小根堆保持着只有n个值,直到打印完m个值结束循环即可

代码:

cpp 复制代码
#include<iostream>
#include<queue>
using namespace std;

const int N = 1e4 + 10;
struct function{
	int A,B,C;
}fun[N];

typedef struct node{
	int val,num,num_x;
	
	bool operator < (const node& x) const
	{
		return val>x.val;
	}
}node; 

int add(int x,int i)
{
	return fun[i].A*x*x + fun[i].B*x + fun[i].C;
}

int main()
{
	int n,m;cin>>n>>m;
	priority_queue<node> heap;
	for(int i=1;i<=n;i++)
	{
		cin>>fun[i].A>>fun[i].B>>fun[i].C;	
		int e = add(1,i);//返回x为1时,当前编号函数的函数值 
		heap.push({e,i,1});
	}
	while(m--)
	{
		node t = heap.top();heap.pop();
		cout<<t.val<<" ";
		int e = add(t.num_x+1,t.num); 
		heap.push({e,t.num,t.num_x+1});
	}
	return 0;
}

6.洛谷---序列合并

如下图所示,结构体保存a值的下标,b值的下标,已经a值b值相加的结果,小根堆比较a值b值相加的结果

代码:

cpp 复制代码
#include<iostream>
#include<queue>
using namespace std;

const int N = 1e5 + 10;
int a[N],b[N];

typedef struct node{
	int i_a,i_b,val;
	
	bool operator < (const node& x) const
	{
		return val>x.val;
	}
}node;

int add(int x,int y)
{
	return a[x]+b[y];
}

int main()
{
	int n;cin>>n;
	priority_queue<node> heap;
	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++)
	{
		int in = add(1,i);
		heap.push({1,i,in});
	}
	while(n--)
	{
		node t = heap.top();heap.pop();
		cout<<t.val<<" ";
		int in = add(t.i_a+1,t.i_b);
		heap.push({t.i_a+1,t.i_b,in});
	}
	return 0;
}

7.洛谷---舞蹈课(难题)

本题难点:

  1. 取出相邻的一对异性以后,他们的左右有可能变为新的一对(B++GB++G -> BG)
  2. 堆中可能存在已经出列的人(一开始BGBG,堆中存了3对BG的差值,出了一队BG后,还剩下2对BG的差值,但实际情况是只剩下一对可以出队的异性了)
  3. 堆中应该存什么?

代码思路:

对于取出异性以后,左右可能成为新的一对,可以用双向链表来解决(如下图所示,当中2个结点被删除以后,左边删除结点的前一个结点连向右边删除结点的后一个结点)

为了判断某人是否已经出列,就像判断一个无根树的结点是否遍历过一样,可以通过一个bool数组来标记

堆中存3个内容,分别是<技术差,左编号,右编号>,堆为小根堆比较对象是技术差

代码:

cpp 复制代码
#include<iostream>
#include<queue>
#include<cmath>
#include<vector>
using namespace std;

const int N = 1e6;
//创建出一个双向链表
int e[N];
int ne[N],pre[N];
int sex[N];//标记男女,男1女0
bool st[N];

typedef struct node{
	int d;//差
	int left,right;//左编号、右编号 
	
	bool operator < (const node& x)const
	{
		if(d!=x.d) return d>x.d;
		else return left>x.left;	
	} 
}node;

int main()
{
	int n;cin>>n;
	for(int i=1;i<=n;i++)
	{
		char in;cin>>in;
		if(in=='B')sex[i]=1;
	}
	for(int i=1;i<=n;i++)
	{
		cin>>e[i];
		ne[i]=i+1;
		pre[i]=i-1;	
	} 
	ne[n]=0;
	//开始存差值
	priority_queue<node> heap;
	for(int i=2;i<=n;i++)
	{
		if(sex[i]!=sex[i-1]) heap.push({abs(e[i]-e[i-1]),i-1,i});	
	} 
	//存完差值开始出人
	vector<node> ret;
	while(heap.size())
	{
		node t = heap.top();heap.pop();
		int d=t.d,l=t.left,r=t.right;
		//判断其中是否已经出队的人
		if(st[l]||st[r]) continue;
		//如果没有,开始存结果+改变链表+更新堆
		ret.push_back(t);
		st[l]=st[r]=true;
		ne[pre[l]] = ne[r];
		pre[ne[r]] = pre[l];
		int left = pre[l],right = ne[r];//获取被删除结点的左右结点 
		if(left&&right&&sex[left]!=sex[right])
			heap.push({abs(e[right]-e[left]),left,right});
	} 
	cout<<ret.size()<<endl;
	for(auto& x:ret)
		cout<<x.left<<" "<<x.right<<endl;
	return 0;	
} 

代码讲解:

  • 可以用 0/1 来模拟表示 G/B ,这个方法也是很常用的一个办法(例如把 */ 转换为 @ 或者直接转换为数字)
  • 因为会存在2对相邻异性的技术差相等的情况,这个时候按照题目要求先输出前面一对,因此在运算符重载时需要考虑 差值相等、差值不相等 2种情况,差值相等时需要根据左编号的大小来进行小根堆存储
  • 双向链表创建时,无需id与p了来标记头结点与新节点的存放位置了,由于静态链表的存放顺序与输入顺序一致,所以直接一边输入一遍存储即可,然后把下标为0处视为头结点
  • 双向链表建立以后,需要把最后一个结点的next指针指向无效结点,要不然链表中会出现一个不该存在的有效节点
  • 因为需要统计完共有几对相邻异性输出以后,再进行是哪两队匹配上的输出,所以可以搞一个vector数组来预先保存结果,最终的size即为共有几对
  • 如下图那样进行双向链表的修改
  • 修改完链表更新堆以前,需要先判断新的左右两个相邻节点是否为异性,同时需要判断左右两个结点是否为无效结点(当删除的是头结点或最后一个结点时,则会出现这种状况)
相关推荐
TL滕2 小时前
从0开始学算法——第十八天(分治算法)
笔记·学习·算法
LYFlied3 小时前
【每日算法】LeetCode 84. 柱状图中最大的矩形
前端·算法·leetcode·面试·职场和发展
Pafey3 小时前
C++的左值引用、右值引用以及转发和完美转发
c++
CoderCodingNo3 小时前
【GESP】C++三级真题 luogu-B4414 [GESP202509 三级] 日历制作
开发语言·c++·算法
Liangwei Lin3 小时前
洛谷 P1955 [NOI2015] 程序自动分析
算法
zwjapple3 小时前
全栈开发面试高频算法题
算法·面试·职场和发展
不穿格子的程序员3 小时前
从零开始写算法——链表篇5:K个一组翻转链表 + 排序链表
算法·链表·分治
青鸟2183 小时前
从资深开发到脱产管理的心态转变
后端·算法·程序员
晨曦夜月3 小时前
笔试强训day7
开发语言·c++·算法