【总结】二叉堆(7.20)

一.概述

堆一般有两个重要的操作,put(往堆中加入一个元素)和get(从堆中取出并删除一个元素)。put一般用来建堆和维护堆,get则是得到最小值。

堆在NOIP竞赛中应用广泛,常用与快速查询最大(最小值),优化各种算法(如:最短路算法、DP算法),是一种效率高,应用广泛的数据结构。

显然,堆只能以一个关键字作为顺序,若一个决策涉及时间和权值,那就必须转换问题,使时间(或权值)的条件弱化,这样就可以愉快地贪心了

下面以大根堆为例:

void up(node x) { heap[++cnt]=x; int p=cnt; while(p>1&&heap[p].val>heap[p>>1].val) { swap(heap[p],heap[p>>1]); p>>=1; } }
void down() { heap[1]=heap[cnt--]; int p=1; while(p*2<=cnt) { int s=p*2; if(s<cnt&&heap[s+1].val>heap[s].val) s++; if(heap[p].val<heap[s].val) swap(heap[p],heap[s]); else break; p=s; } }

二.题目

http://222.180.160.110:1024/contest/630

A.堆

题目描述
如题,初始小根堆为空,我们需要支持以下3种操作:

操作1: 1 x 表示将x插入到堆中

操作2: 2 输出该小根堆内的最小数

操作3: 3 删除该小根堆内的最小数

解析:板子题

#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=100005; int n,a[maxn],heap[maxn],cnt=0; void up(int p) { while(p>1&&heap[p]<heap[p>>1]) { swap(heap[p],heap[p>>1]); p>>=1; } } void down(int p) { while(p*2<=cnt) { int s=p*2; if(s<cnt&&heap[s+1]<heap[s]) s++; if(heap[p]>heap[s]) swap(heap[p],heap[s]); else break; p=s; } } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { int ok,x; scanf("%d",&ok); if(ok==1) { scanf("%d",&x); heap[++cnt]=x; up(cnt); } else if(ok==2) printf("%d\n",heap[1]); else { heap[1]=heap[cnt--]; down(1); } } }

B.鱼塘钓鱼(fishing)

解析:我们可以发现,假设能钓到鱼的数量仅和已钓鱼的次数有关,且每次钓鱼的时间都是整数分钟。 则每次钓鱼都要取当前最大值。但本题路程是变量,可以枚举到达的最远鱼塘,显然不会走回头路(因为与钓鱼先后无关),我们可以加入[1,i]的鱼塘,令 t = m − l [ i ] t=m-l[i] t=ml[i],其中 l l l表示路程所花费时间。每次从二叉堆中取出并删除最大值,修改后再加入,重复 t t t次就得到了当前最大价值。

#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn=105; struct node{ int xh,val; }heap[maxn],st,now; int n,m,cnt,a[maxn],b[maxn],l[maxn],ans; void up(node x) { heap[++cnt]=x; int p=cnt; while(p>1&&heap[p].val>heap[p>>1].val) { swap(heap[p],heap[p>>1]); p>>=1; } } node down() { node res=heap[1]; heap[1]=heap[cnt--]; int p=1; while(p*2<=cnt) { int s=p*2; if(s<cnt&&heap[s+1].val>heap[s].val) s++; if(heap[p].val<heap[s].val) swap(heap[p],heap[s]); else break; p=s; } return res; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=n;i++) scanf("%d",&b[i]); for(int i=2;i<=n;i++) { scanf("%d",&l[i]); l[i]+=l[i-1]; } scanf("%d",&m); for(int i=1;i<=n;i++) { cnt=0; int t=m-l[i],tot=0; if(t<=0) break; for(int j=1;j<=i;j++) { st.xh=b[j]; st.val=a[j]; up(st); } while(t--) { st=down(); if(st.val<=0) break; tot+=st.val; st.val-=st.xh; up(st); } ans=max(ans,tot); } printf("%d",ans); }

二叉堆其实很擅长于求前m个大的决策,我们通常是先求最大,再求次大,以此推广。或者先求局部最优解,再缩小问题规模,直到问题解决。(贪心思想)

C.[CTSC2007]数据备份Backup

显然可以有70pts的dp代码:

#include <cstdio> #include <algorithm> #include <cstring> #define ll long long using namespace std; const int maxn = 100005; ll n, k, a[maxn], f[maxn], dp[3][maxn]; int main() { memset(dp, 0x3f3f3f3f, sizeof(dp)); scanf("%lld%lld", &n, &k); for (int i = 1; i <= n; i++) { scanf("%lld", &a[i]); f[i] = a[i] - a[i - 1]; } for (int i = 0; i <= 2; i++) dp[i][0] = 0; for (int i = 2; i <= n; i++) { //只能滚阶段 for (int j = 1; j <= k; j++) { dp[i % 3][j] = min(dp[(i - 1) % 3][j], dp[(i - 2) % 3][j - 1] + f[i]); } } printf("%lld", dp[n % 3][k]); }

不难想到, 为了使布线长度尽量小,每对布线的办公楼一定是相邻的

所以我们可以在读入时计算差分数组保存每相邻两个办公楼的距离

这样问题转化为, 在差分数组中找k个数,满足k个数之和最小且互不相邻

设差分数组为b[], 其中最小的数为b[i]

显然最优解必定是一下其中一种

1.包含b[i]以及除b[i-1]和b[i+1]的数

2.包含b[i-1]和b[i+1]以及除b[i],b[i-2],b[i+2]

从这一点扩展, 可以先取b[i],并以b[i-1]+b[i+1]-b[i]替换,

然后在新数列中继续重复k-1次得到最后结果

这样若b[i]不属于最优解,则b[i-1]+b[i+1]-b[i]必定被选,满足了上述第二种情况

更具体做法是, 将原差分数组每个值插入堆, 并将数组以链表串起来

每次取堆顶最小值更新答案,并删除该值,

设最小值编号为i, 那么再插入b[ pre[i] ]+b[ nxt[i] ]-b[i], 并更新链表

重复k次即得最优解

#include<bits/stdc++.h> using namespace std; const int maxn=100005; const int inf=0x3f3f3f3f; void read(long long &x) { int f=1;x=0;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') {x=(x<<1)+(x<<3)+(c^48);c=getchar();} x*=f; } long long n,k,m,ans,l[maxn],r[maxn],p[maxn]; bool vis[maxn]; priority_queue<pair<long long,int> > q; int main() { long long last,x; read(n),read(k); for(int i=1;i<=n;i++) { read(x); if(i>1) p[++m]=x-last; last=x; } for(int i=1;i<=m;i++) { q.push(make_pair(-p[i],i)); l[i]=i-1; r[i]=i+1; } p[0]=p[n]=inf;//为什么要这样赋值? for(int i=1;i<=k;i++) { while(vis[q.top().second]) q.pop(); int x=q.top().second;q.pop(); ans+=p[x]; int li=l[x],ri=r[x]; vis[li]=vis[ri]=1; p[x]=p[li]+p[ri]-p[x]; l[x]=l[li];r[x]=r[ri];r[l[li]]=x;l[r[ri]]=x; q.push(make_pair(-p[x],x)); } printf("%lld",ans); }

__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530416.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(43)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示