数据结构·堆
堆
堆是一种数据结构。没了
堆是一种树形结构,堆顶始终保持为所有元素的最优值,所以常常运用于贪心中。
大根堆的根为堆的最大值,小根堆的根为堆的最小值。堆一般用二叉树实现。
【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.矩阵选数
数据存在问题