一.概述
堆一般有两个重要的操作,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=m−l[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);
}