数据结构·堆

堆是一种数据结构。没了

堆是一种树形结构,堆顶始终保持为所有元素的最优值,所以常常运用于贪心中。

大根堆的根为堆的最大值,小根堆的根为堆的最小值。堆一般用二叉树实现。


【YbtOj】题解

A.合并果子

可以贪心地想到每次选取代价最小的两堆进行合并,再将新的一堆放入所有待选堆中。每次操作用小根堆维护最小代价即可,复杂度\(O(n\ log\ n)\)

关于\(O(n)\)的做法:我们可以想到,对于新合并成的一堆果子,其代价一定是单调递增的,于是可以用另一个队列存储新合成的果子堆,每次操作取原队列与第二个队列的队头较小值,这样不用堆便维护了队列的单调性。为了确保\(O(n)\)的复杂度,刚开始直接上简单粗暴的桶排即可 。

B.序列合并

用大根堆维护目前所选数的最大值,因为给出序列已具有单调性,所以直接for循环两个指针分别从前往后分别扫两个序列即可。若目前没选够\(n\)个数,那么直接插入\(a_{i}+b_{j}\);若已选够,那么就与目前最大值比较判断是否更优。均摊下来复杂度为\(O(n\ log\ n)\),不会炸(此处存疑)

AC代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n;
int a[N],b[N];
priority_queue <int> q;
stack <int> s;

signed main()
{
	scanf("%lld",&n);
	for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for (int i=1;i<=n;i++) scanf("%lld",&b[i]);
	
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=n;j++)
		{
			int t=a[i]+b[j];
			if (q.size()<n) {  q.push(t); continue;  }
			if (t<q.top()) {  q.pop(); q.push(t); continue;  }
			break;
		}
	}
	
	while (!q.empty()) {  s.push(q.top()); q.pop();  }
	while (!s.empty()) {  printf("%lld ",s.top()); s.pop();  }
	return 0;
}

C.龙珠游戏

看到神似“删除”的操作,想到链表。用大根堆维护目前队列的最大值,若当前堆顶\(i\)没有走过且不是队列最后一个元素,那么与\(nxt_{i}\)一同出队,并链表叽里呱啦操作一下、标记已出队即可。

来自sxht dalao:不用堆优化就能做到\(O(n)\),因为\(a\)中元素两两不同,根据\(a_{i}\)建链表即可。

AC代码(带无意义的堆优化)
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define mp make_pair
using namespace std;
const int N=1e5+5;
int n;
int a[N];
int nxt[N],pre[N];
bool flag[N];
priority_queue <pii> q;
queue <int> ans;

signed main()
{
	scanf("%lld",&n);
	for (int i=1;i<=n;i++) { scanf("%lld",&a[i]); q.push(mp(a[i],i)); }
	for (int i=1;i<n;i++) nxt[i]=i+1;
	for (int i=2;i<=n;i++) pre[i]=i-1;
	
	while (ans.size()<n)
	{
		while (!q.empty()&&(flag[q.top().second]||nxt[q.top().second]==0)) q.pop();
		int t1=q.top().second,t2=nxt[t1];
		flag[t1]=true,flag[t2]=true;
		pre[nxt[t2]]=pre[t1],nxt[pre[t1]]=nxt[t2];
		ans.push(a[t1]),ans.push(a[t2]);
		q.pop();
	}
	
	while (!ans.empty()) {  printf("%lld ",ans.front()); ans.pop();  }
	return 0;
}

D.工作安排

很典的一道反悔贪心。为了能完成尽量多的工作,我们每次肯定是取截止时间更靠前的工作;但若是有一个工作靠前,但获利少,而后面存在一个获利更多的工作,这样的贪心选择就会不那么优。于是我们会想到,每次对于一个获利更多但没时间的工作,将已选的工作中获利最少的替换掉,这样一定是更优的。

所以,刚开始按照截止日期升序排序,用小根堆维护目前已选的最小的 \(P_{i}\) 就行。至于选了多少个数?堆内元素数量就是选的数的个数

AC代码
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N=1e5+5;
int n;
struct node{
	int d,p;
}a[N];
priority_queue < pii,vector <pii>,greater<pii> > q;
int ans;

bool cmp(node x,node y) { return x.d<y.d; }
signed main()
{
	scanf("%lld",&n);
	for (int i=1;i<=n;i++) scanf("%lld%lld",&a[i].d,&a[i].p);
	
	sort(a+1,a+1+n,cmp);
	for (int i=1;i<=n;i++)
	{
		if (q.size()<a[i].d)
		{
			ans+=a[i].p;
			q.push({a[i].p,a[i].d});
		}
		else
		{
			if (a[i].p>q.top().first)
			{
				ans=ans-q.top().first+a[i].p;
				q.pop();
				q.push({a[i].p,a[i].d});
			}
		}
	}
	
	printf("%lld",ans);
	return 0;
}

E.家庭作业

也是很典的一道题呢(让我想起来了一个月前让我痛苦万分的分冰棍!)

先按照截止时间排序,每次尽量不喝奶茶,若对于当前 \(d_{i}\) 无法满足,那么前面就一定要喝奶茶了。为了尽量少喝,我们就要选性价比最高的奶茶,用大根堆维护即可;而喝奶茶不能喝到“时光倒流”,所以还需要维护每种奶茶的剩余量。

AC代码
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,double>
#define mp make_pair
using namespace std;
const int N=2e5+5;
int n;
struct node{
	int l,t,d;
}a[N]; 
int tme;
double ans;
priority_queue < pii > q;

bool cmp(node x,node y) {  return x.d<y.d;  } 
signed main()
{
	scanf("%lld",&n);
	for (int i=1;i<=n;i++) scanf("%lld%lld%lld",&a[i].l,&a[i].t,&a[i].d);
	
	sort(a+1,a+1+n,cmp);
	for (int i=1;i<=n;i++)
	{
		q.push(mp(a[i].l,1.0*a[i].t));//又多了一种奶茶 
		tme+=a[i].t;
		while (tme>a[i].d)//还得喝奶茶 
		{
			int fst=q.top().first,d=tme-a[i].d;
			double scd=q.top().second;
			q.pop();
			if (scd>=d)//要补的 
			{
				ans+=1.0*d/(1.0*fst);
				if (scd!=d) q.push(mp(fst,scd-1.0*d));
				tme=a[i].d;
				break;
			}
			else//不够 
			{
				tme-=scd;
				ans+=scd/(1.0*fst);
			}
		}
	}
	printf("%.2lf",ans);
	return 0;
}

F.选数游戏

事实证明不及时写思路不够清晰的题的题解,过一段时间就更不记得了

AC代码
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N=1e6+5;
int n,m,k;
int a[N];
priority_queue < int,vector <int>,greater<int> > q;
int cnt,sum;
int ans;

signed main()
{
	scanf("%lld%lld%lld",&n,&m,&k);
	for (int i=1;i<=m;i++) scanf("%lld",&a[i]);
	
	int maxx=0;
	for (int i=1;i<=min(m,k);i++)
	{
		q.push(a[i]);
		cnt+=n,sum+=n*a[i];
		while (cnt>k&&!q.empty())
		{
			cnt-=n-1,sum-=(n-1)*q.top();
			maxx=q.top();
			q.pop();
		}
		ans=max(ans,sum+min(n,k-cnt)*maxx);
	}
	
	printf("%lld",ans);
	return 0;
}

G.内存管理

直接模拟即可。小根堆维护当前编号最小的未分配内存块,队列维护被分配的内存块顺序, \(t_{i}\) 维护每个已分配内存块最后被操作的时间,每次操作将不合法的编号出队即可。

AC代码
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define mp make_pair
#define fst first
#define scd second
using namespace std;
const int N=3e4+5;
int t[N];//对于被占用的内存块i,最后一次被操作的时间
priority_queue < int,vector <int>,greater<int> > q; 
queue <pii> out;

signed main()
{
	for (int i=1;i<=30000;i++) q.push(i);
	
	int x,y;
	char c;
	while (cin>>x>>c)
	{
		while (!out.empty()&&out.front().scd<=x-600)
		{
			if (out.front().scd==t[out.front().fst])
			{
				t[out.front().fst]=0;
				q.push(out.front().fst);
			}
			out.pop();
		}
		if (c=='+')
		{
			cout<<q.top()<<'\n';//分配编号最小的空闲的
			t[q.top()]=x;
			out.push(mp(q.top(),x));
			q.pop(); 
		}
		if (c=='.')
		{
			cin>>y;
			if (t[y]) cout<<'+'<<'\n';
			else cout<<'-'<<'\n';
			if (t[y]) t[y]=x;
			out.push(mp(y,x));
		}
	}
	return 0;
}

H.火车载客

待补

I.矩阵选数

数据存在问题

posted @ 2024-11-20 23:22  还是沄沄沄  阅读(3)  评论(0编辑  收藏  举报