首页 > 其他分享 >【寻迹#5】堆

【寻迹#5】堆

时间:2024-11-04 14:22:56浏览次数:1  
标签:maxx int 寻迹 结点 pop flag include

一、堆

1.结构

从二叉堆的结构说起,它是一棵二叉树,并且是完全二叉树,每个结点中存有一个元素(或者说,有个权值)。

堆性质:父亲的权值不小于儿子的权值(大根堆)。同样的,我们可以定义小根堆。

2.过程

(1)插入

插入操作是指向二叉堆中插入一个元素,要保证插入后也是一棵完全二叉树。

最简单的方法就是,最下一层最右边的叶子之后插入。

如果最下一层已满,就新增一层。

插入之后可能会不满足堆性质?

向上调整:如果这个结点的权值大于它父亲的权值,就交换,重复此过程直到不满足或者到根。

inline void Ins(int x)
{
	Heap[++tot]=x;
	up(tot);
}

可以证明,插入之后向上调整后,没有其他结点会不满足堆性质。

向上调整的时间复杂度是 \(O(\log n)\) 的。

inline void up(int x)
{
	while(x>1)
	{
		if(Heap[x]<Heap[x/2]) { swap(Heap[x],Heap[x/2]);x>>=1; }
		else break;
	}
}

(2)删除

删除操作指删除堆中最大的元素,即删除根结点。

但是如果直接删除,则变成了两个堆,难以处理。

所以不妨考虑插入操作的逆过程,设法将根结点移到最后一个结点,然后直接删掉。

然而实际上不好做,我们通常采用的方法是,把根结点和最后一个结点直接交换。

于是直接删掉(在最后一个结点处的)根结点,但是新的根结点可能不满足堆性质……

向下调整:在该结点的儿子中,找一个最大的,与该结点交换,重复此过程直到底层。

inline void pop()
{
	Heap[1]=Heap[tot--];
	down(1);
}

可以证明,删除并向下调整后,没有其他结点不满足堆性质。

时间复杂度 \(O(\log n)\) 。

inline void down(int x)
{
	int p=2*x;
	while(p<=tot)
	{
		if(p<tot&&Heap[p]>Heap[p+1]) p++;
		if(Heap[p]<Heap[x]) { swap(Heap[p],Heap[x]);x=p;p=2*x; }
		else break;
	}
}

(3)改变某个点的权值

比较显然,直接修改,然后向上或向下调整一次即可,时间复杂度 \(O(\log n)\) 。

二、题单

1.【模板】堆

传送门

思路:

模板题,试试水。

代码:

#include<bits/stdc++.h>
using namespace std;
#define N 1000050
int Heap[N];
int n,tot,op;
inline void up(int x)
{
	while(x>1)
	{
		if(Heap[x]<Heap[x/2]) { swap(Heap[x],Heap[x/2]);x>>=1; }
		else break;
	}
}
inline void down(int x)
{
	int p=2*x;
	while(p<=tot)
	{
		if(p<tot&&Heap[p]>Heap[p+1]) p++;
		if(Heap[p]<Heap[x]) { swap(Heap[p],Heap[x]);x=p;p=2*x; }
		else break;
	}
}
inline int Gettop() { return Heap[1]; }
inline void pop()
{
	Heap[1]=Heap[tot--];
	down(1);
}
inline void Ins(int x)
{
	Heap[++tot]=x;
	up(tot);
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		int x;
		cin>>op;
		if(op==2) cout<<Gettop()<<endl;
		else if(op==3) pop();
		else 
		{
			cin>>x;
			Ins(x);
		}
	}
	return 0;
}

2.合并果子

传送门

思路:

经典题,用堆,直接用stl即可,注意stl默认大根堆,压到堆里的时候压负值即可。

每次取堆内的前两小元素合并,合并完了再压到堆里,

重复此操作,直到堆为空。

代码:

#include<iostream> 
#include<queue>
#include<bits/stdc++.h>
using namespace std;
#define N 10050
int a[N],ans,n;
priority_queue<int>Q;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		Q.push(-a[i]);
	}
	while(Q.size()>=2)
	{
		int x=-Q.top();Q.pop();
		int y=-Q.top();Q.pop();
		ans+=x+y;
		Q.push(-(x+y));
	}
	cout<<ans<<endl;
	return 0;
}

3.最小函数值

传送门

思路:

首先看到自变量的取值范围,发现 \(x\in\mathbb{N+}\) ,所以 \(x=2\) 时的函数值一定会比 \(x=1\) 时的函数值大。

先把所有函数在 \(x=1\) 时的函数值压进去,然后重复 \(m-1\) 次,每次取出堆顶最小的函数值并输出,然后将其 \(x+1\) 后的函数值计算出来压到堆里,

最后再输出一次堆顶的函数值即可。

代码:

#include<bits/stdc++.h>
using namespace std;
#define N 10050
int n,m;
int a[N],b[N],c[N],x[N];
priority_queue< pair<int,int> >Q;
inline int Cal(int i) { return a[i]*x[i]*x[i]+b[i]*x[i]+c[i]; }
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i]>>b[i]>>c[i];
	for(int i=1;i<=n;i++)
	{
		x[i]=1;
		Q.push(make_pair(-Cal(i),i));
	}
	for(int i=1;i<m;i++)
	{
		int p=-Q.top().first;int q=Q.top().second;
		cout<<p<<" ";
		x[q]++;
		Q.pop();
		Q.push(make_pair(-Cal(q),q));
	}
	cout<<-Q.top().first<<endl;
	return 0;
}

4.中位数

传送门

思路:

从这道题开始就有点意思了,这里利用了堆的一个应用——对顶堆。

对顶堆是一组堆(两个堆),常用于解决求第 \(k\) 小的问题,

对顶堆包含两个堆,一个大根堆一个小根堆,

其中,大根堆存储小于中间值的数,小根堆存储大于中间值的数,

大根堆堆顶是小于中间值数里的最大值,小根堆顶是大于中间值的数的最小值

对于这道题来说,当两个堆的大小相差 \(1\) 的时候,两个堆中元素数量较多的那个堆的堆顶元素就是我们要求的中位数,

先考虑怎么建堆,每次把大于上一个中位数的数压到小根堆中,把小于上一个中位数的数压到大根堆中,初始的时候中位数就是 \(a[1]\) ,

在考虑怎么维护对顶堆,在我们建完堆之后进行调整,每次取出数量较多的那个堆中的堆顶元素,将其压入另一个堆中,重复此操作,直到两个堆元素数量差 \(1\) 。

真正在操作中,中位数对应的值可能会反复出堆入堆,具体看代码实现。

代码:

#include<bits/stdc++.h>
using namespace std;
#define N 100050
int n,mid;
int a[N];
priority_queue<int>Q;//大根堆 (默认) 堆顶:小于中位数的最大值 
priority_queue<int>H;//小根堆 堆顶:大于中位数的最小值 
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	cout<<a[1]<<endl;
	mid=a[1];
	for(int i=2;i<=n;i++)
	{
		if(a[i]>mid) H.push(-a[i]);
		else Q.push(a[i]);
		if(i%2)
		{
			while(Q.size()!=H.size())
			{
				if(Q.size()>H.size())
				{
					H.push(-mid);
					mid=Q.top();
					Q.pop();
				}
				else
				{
					Q.push(mid);
					mid=-H.top();
					H.pop();
				}
			}
			cout<<mid<<endl;
		}
	}
	return 0;
} 

5.黑匣子

传送门

思路:

第 \(k\) 小问题,想到用对顶堆,

考虑一个堆里保证只有 \(k-1\) 个元素,则另一个堆的堆顶即为答案。

代码:

#include<bits/stdc++.h>
using namespace std;
#define N 200050
int n,m,a[N],u[N];
int mid,k,vis[N]; 
priority_queue<int>Q;//大根堆 维护小于mid的最大值 
priority_queue<int>H;//小根堆 维护大于mid的最小值 
int main()
{
	cin>>m>>n;
	for(int i=1;i<=m;i++) cin>>a[i];
	for(int i=1;i<=n;i++) { cin>>u[i];vis[u[i]]++; }
	for(int i=1;i<=n;i++)
	{
		while(k<u[i])
		{
			k++;
			Q.push(a[k]);
			H.push(-Q.top());
			Q.pop();
		}
		cout<<-H.top()<<endl;
		Q.push(-H.top());
		H.pop();
	}
	return 0;
}
/*
Query:
7 4
3 1 -4 2 8 -1000 2
1 2 6 6
Ans:
3 3 1 2

这个写的确实巧妙,不妨这么来理解:压入堆中的时候,Q压进去一个就把top压到H里,保证Q个数不变,此时H堆顶为答案,输出答案后下一次查询时k要加1,所以把H的堆顶压到Q里,保证Q在下一轮个数是正确的。

*/

6.蚯蚓

传送门

思路:

一开始考虑用堆解决,不能暴力给每个蚯蚓加长度 \(q\) ,

用类似于线段树懒标记的方法,可以时间复杂度 \(O(m\log m)\) 通过。

接下来考虑更优的做法,考虑维护三个队列,

第一个队列是初始的蚯蚓,第二个队列是切下来的左端,第三个队列是切下来的右端。

现在我们将初始所有蚯蚓按降序排列后压入第一个队列,

那么我们可以发现,每次取出第一个队列的蚯蚓切完以后,放入二三队列后,二三队列也具有单调递减的特性,

所以我们每次取蚯蚓的时候,在三个队列的头中取一个最大的就好了。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
#define N 100050
typedef long long ll;
ll n,m,q,u,v,t,a[N];
ll maxx,flag,l,r,cnt;
queue<ll>Q;queue<ll>L;queue<ll>R;
priority_queue<ll>H;
inline bool cmp(ll a,ll b) { return a>b; }
int main()
{
	cin>>n>>m>>q>>u>>v>>t;
	for(int i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++) Q.push(a[i]);
	for(int i=1;i<=m;i++)
	{
		maxx=-0x3f3f3f3f;flag=0;
		if(Q.size())
			if(Q.front()>maxx) { maxx=Q.front();flag=1; }
		if(L.size())
			if(L.front()>maxx) { maxx=L.front();flag=2; }
		if(R.size())
			if(R.front()>maxx) { maxx=R.front();flag=3; }
		if(flag==1) Q.pop();
		else if(flag==2) L.pop();
		else if(flag==3) R.pop();	
		maxx+=(i-1)*q;
		l=maxx*u/v;r=maxx-l;
		if(!(i%t)) cout<<maxx<<" ";
		L.push(l-i*q);R.push(r-i*q);
	}
	puts("");
	cnt=1;
	while(cnt)
	{
		maxx=-0x3f3f3f3f;flag=0;
		if(Q.empty()&&L.empty()&&R.empty()) break;
		if(Q.size())
			if(Q.front()>maxx) { maxx=Q.front();flag=1; }
		if(L.size())
			if(L.front()>maxx) { maxx=L.front();flag=2; }
		if(R.size())
			if(R.front()>maxx) { maxx=R.front();flag=3; }
		if(flag==1) Q.pop();
		else if(flag==2) L.pop();
		else if(flag==3) R.pop();	
		if(!(cnt%t)) cout<<maxx+m*q<<" ";
		cnt++;
	} 
	return 0;
} 

标签:maxx,int,寻迹,结点,pop,flag,include
From: https://www.cnblogs.com/Cybersites/p/18525123

相关文章

  • 【寻迹#4】并查集
    并查集一、并查集并查集是一种用于管理元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素。顾名思义,并查集支持两种操作:合并(Union):合并两个元素所属集合(合并对应的树)查询(Find):查询某个元素所属集合(查询对应的树的根节点),这可以用于判......
  • 【寻迹#3】 哈希与哈希表
    哈希和哈希表哈希算法是通过一个哈希函数\(\operatornameH\),将一种数据(包括字符串、较大的数等)转化为能够用变量表示或是直接就可作为数组下标的数,通过哈希函数转化得到的数值我们称之为哈希值。通过哈希值可以实现快速查找和匹配。哈希算法具体应用有:字符串\(\operatorname......
  • 【STM32】寻迹小车项目复盘
    寻迹小车项目复盘前言复盘简述项目无思路,无大局观描述复盘项目无架构描述复盘下次项目改进思路DEBUG无思路前言博主近日首次完成了一个简单的循迹小车。但让我意外的是,在我上手如此简单的项目时,我的思路却十分混乱,开发过程毫无逻辑,虽说跌跌撞撞的做出来了,但效率低......
  • 【寻迹】二分与三分
    二分与三分二分是一种常用且非常精妙的算法。(英才计划甚至还水了一篇文章)三分法则可以用来解决单峰函数的极值以及相关问题一、二分二分法,在一个单调有序的集合或函数中查找一个解,每次均分为左右两部分,判断解在哪一个部分后调整上下界。每次二分都会舍弃一半区间,因此效率比较高......
  • Arduino 驱动红外寻迹模块
    以下是使用ArduinoUnoR3驱动红外寻迹模块的详细说明、接线图和代码示例。所需材料ArduinoUnoR3红外寻迹模块(例如TCRT5000)面包板和连接线接线步骤连接红外寻迹模块:红外寻迹模块通常有一个发射器和一个接收器。将红外寻迹模块的VCC引脚连接到ArduinoUno的5V引脚。......
  • NanoFramework操作ESP32(一)_基础元器件篇(三十九)_ TCRT5000红外寻迹模块
    产品用途:1、电度表脉冲数据采样2、传真机碎纸机纸张检测3、障碍检测4、黑白线检测产品介绍:1、采用TCRT5000红外反射传感器2、检测反射距离:1mm~25mm适用3、工作电压:5V4、输出形式:数字信号(0和1)5、设有固定螺栓孔,方便安装6、小板PCB尺寸:3.5cmx1cm7、单重:4.5g功能介绍......