【总结】二叉堆(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);
}

猜你喜欢

转载自blog.csdn.net/cqbzlydd/article/details/107473668