数据结构·堆

堆是一种数据结构。没了

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

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


【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.选数游戏

若选了第 \(i\) 列的数,那么它有两种贡献方式:一是使得可以到达 \(i\) 后贡献更大的列,二是它的值直接贡献。因此,若一列有取到的数,一定是只选一个或全都选。

于是,从左到右遍历数列,遍历到第 \(i\) 列时,从第 \(1\)\(i\) 列一定都有被选的数,此时 \(sum\) 相当于是 选到第 \(i\) 列的最大贡献和,所有 \(sum\) 的最大值就是最终答案。

到第 \(i\) 列时,将其所有的数都计入 \(cnt\)\(sum\) ,若溢出,那么每次去除最小值即可,用小根堆维护。

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.火车载客

写了n次,果然没我想的那么简单啊啊啊

贪心考虑,一定是让第 \(k\) 组所有人上车最优。若所有人都上车人数超过限制,那么对于时间 \([s,t]\) 内车上的人员,需要下车的人数就是 \(sum-c\)\(sum\)表示当前车上人数),而为了最优,一定是使下车时间靠后的人下车,这里就可以用堆维护。

将第 \(k\) 组分成上车、下车两部分分别处理。对于第 \(id\) 次,若当前下车,那么就将答案加上该组人数,再将 \(sum\) 减去 \(id\) 的人数;若当前上车,就在限制 \(c\) 内尽量往里面塞人。需要注意在塞人前,要提前把在该站下车的人下车,不然会影响塞人。如果 \(id\) 还可以再塞人且更优,那就用第 \(id\) 次的人替换先前不优的人。

AC代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5;
int k,n,c;
struct train
{
	int s,t,p,id;//p:还有多少人可上车 id:已上车多少人
	bool operator < (const train &a) const {  return t<a.t;  }
}a[N];
struct node
{
	int tme,id,op;
	bool operator < (const node &a) const 
	{
		if (tme!=a.tme) return tme<a.tme;
		else return op>a.op;
	}
}b[N<<1];
int tol;
priority_queue <train> q;
int ans,sum;

signed main()
{
	scanf("%lld%lld%lld",&k,&n,&c);
	for (int i=1;i<=k;i++)
	{
		scanf("%lld%lld%lld",&a[i].s,&a[i].t,&a[i].p);
		b[++tol]={a[i].s,i,0};
		b[++tol]={a[i].t,i,1};
	}
	
	sort(b+1,b+1+tol);
	for (int i=1;i<=tol;i++)
	{
		int id=b[i].id;
		if (b[i].op==1)
		{
			ans+=a[id].id ,sum-=a[id].id;
			a[id].id=0;
			continue;
		}
		//该下下 
		while (!q.empty()&&q.top().t<b[i].tme) {  a[q.top().id].id=0 ; q.pop();  }
		//尽量上 
		while (sum<c&&a[id].p)
		{
			int add=min(c-sum,a[id].p);
			a[id].id+=add,a[id].p-=add;
			q.push((train){a[id].s,a[id].t,add,id});
			sum+=add;
		}
		//更优就上 
		while (!q.empty()&&a[id].p&&a[id]<q.top())
		{
			train tmp=q.top();
			q.pop();
			
			int add=min(a[id].p,tmp.p);
			tmp.p-=add;
			a[tmp.id].id-=add;
			a[id].id+=add,a[id].p-=add;
			
			if (tmp.p) q.push(tmp);
			q.push((train){a[id].s,a[id].t,add,id});
		}
	}
	printf("%lld",ans);
	return 0;
}

I.矩阵选数

数据存在问题

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