「数据结构」第1章 二叉堆课堂过关

「数据结构」第1章 二叉堆课堂过关

堆-结构体模板

struct Heap {
	#define max_(_ , __) ((_) < (__) ? (_) : (__))
	int siz;
	int a[nn * 2];
	bool ty;
	inline void swap_(int x , int y) {int tmp = a[x] ; a[x] = a[y] ; a[y] = tmp;}
	void clear() {
		siz = 0 , ty = 0;
		memset(a , 0 , sizeof(a));
	}
	inline void push(int dat) {
		a[++siz] = dat;
		int p = siz;
		while(p > 1 && (!(a[p >> 1] < a[p]) ^ ty))
			swap_(p >> 1 , p) , p >>= 1;
	}
	inline void pop() {
		swap_(1 , siz);	--siz;
		int p = 1 , tmp;
		//这里可读性比较差,注意向下调节时当前节点与两个子节点比较
		while(p * 2 <= siz && (!(a[p] < a[tmp = ( p * 2 + 1 > siz ? p * 2 : (a[p * 2] < a[p * 2 + 1] ^ ty ? p * 2 : p * 2 + 1) ) ] ) ^ ty)) 
			swap_(tmp , p) , p = tmp;
	}
	inline int top() {
		return siz == 0 ? 0 : a[1];
	}
	inline bool empty(){return siz == 0;}
}h;

A. 【例题1】合并果子

题目

代码

手工堆

#include <iostream>
#include <cstdio>
#include <cstring>
#define nn 100010
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}
struct Heap {
	#define max_(_ , __) ((_) < (__) ? (_) : (__))
	int siz;
	int a[nn * 2];
	bool ty;
	inline void swap_(int x , int y) {int tmp = a[x] ; a[x] = a[y] ; a[y] = tmp;}
	void clear() {
		siz = 0 , ty = 0;
		memset(a , 0 , sizeof(a));
	}
	inline void push(int dat) {
		a[++siz] = dat;
		int p = siz;
		while(p > 1 && (!(a[p >> 1] < a[p]) ^ ty))
			swap_(p >> 1 , p) , p >>= 1;
	}
	inline void pop() {
		swap_(1 , siz);	--siz;
		int p = 1 , tmp;
		//这里可读性比较差,注意向下调节时当前节点与两个子节点比较
		while(p * 2 <= siz && (!(a[p] < a[tmp = ( p * 2 + 1 > siz ? p * 2 : (a[p * 2] < a[p * 2 + 1] ^ ty ? p * 2 : p * 2 + 1) ) ] ) ^ ty)) 
			swap_(tmp , p) , p = tmp;
	}
	inline int top() {
		return siz == 0 ? 0 : a[1];
	}
	inline bool empty(){return siz == 0;}
}h;
int n;
int main() {
	h.clear();
	n = read();
	for(int i = 1 ; i <= n ; i++)
		h.push(read());
	int ans = 0;
	while(h.siz > 1) {
		int add = 0;
		add += h.top();	h.pop();
		add += h.top();	h.pop();
		ans += add;
		h.push(add);
	}
	cout << ans;
	return 0;
}

STL优先队列

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define nn 100010

using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}
priority_queue <int> h;
int n;
int main() {
	n = read();
	for(int i = 1 ; i <= n ; i++)
		h.push(-read());//默认大根堆
	int ans = 0;
	while(h.size() > 1) {
		int add = 0;
		add += h.top();	h.pop();
		add += h.top();	h.pop();
		ans += add;
		h.push(add);
	}
	cout << -ans;
	return 0;
}

B. 【例题2】序列合并

题目

思路

\(b\)数组从小到大排序的

显然,对于同一个\(i\),若\(j\)越大,\(a_i+b_j\)越大.题目要求的是前\(n\)小,所以我们设一个\(p\)数组,初始值全部为1,将\(a_i+b_{p_i}(1\le i\le n)\)\(i\)捆绑起来压入小根堆,进行\(n\)次循环,每次从堆中取出最小的\(a_i+b_{p_i}\),\(p_i=p_i+1\),若\(p_i\le n\),将\(a_i+b_{p_i}\)压入小根堆(注意\(p_i\))已发生变化

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define nn 100010
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}
struct Heap {
	#define max_(_ , __) ((_) < (__) ? (_) : (__))
	int siz;
	int a[nn * 2];
	int dat[nn * 2];
	
	bool ty;
	inline void swap_(int x , int y) {int tmp = a[x] ; a[x] = a[y] ; a[y] = tmp;	tmp = dat[x] ; dat[x] = dat[y] ; dat[y] = tmp;}
	void clear() {
		siz = 0 , ty = 0;
		memset(a , 0 , sizeof(a));
		memset(dat , 0 , sizeof(dat));
	}
	inline void push(int a_ , int dat_) {
		a[++siz] = a_;
		dat[siz] = dat_;
		int p = siz;
		while(p > 1 && (!(a[p >> 1] < a[p]) ^ ty))
			swap_(p >> 1 , p) , p >>= 1;
	}
	inline void pop() {
		swap_(1 , siz);	--siz;
		int p = 1 , tmp;
		while(p * 2 <= siz && (!(a[p] < a[tmp = ( p * 2 + 1 > siz ? p * 2 : (a[p * 2] < a[p * 2 + 1] ^ ty ? p * 2 : p * 2 + 1) ) ] ) ^ ty)) 
			swap_(tmp , p) , p = tmp;
	}
	inline int top() {
		return siz == 0 ? 0 : a[1];
	}
	inline int top_dat() {
		return siz == 0 ? 0 : dat[1];
	}
	inline bool empty(){return siz == 0;}
}h;
int n;
int a[nn];
int b[nn];
int p[nn];
int main() {
	h.clear();
	n = read();
	for(int i = 1 ; i <= n ; i++)
		a[i] = read();
	for(int i = 1 ; i <= n ; i++)
		b[i] = read();
	
	
	for(int i = 1 ; i <= n ; i++) {
		p[i] = 1;
		h.push(a[i] + b[p[i]] , i);
	}
	
	for(int i = 1 ; i <= n ; i++) {
		printf("%d " , h.top());
		int k = h.top_dat();
		h.pop();
		p[k]++;
		if(p[k] <= n)
			h.push(a[k] + b[p[k]] , k);
	}
	return 0;
}

C. 【例题3】龙珠游戏

题目

传送门

思路

首先读懂题目:输入的是一个排列

其次,这题和堆好像一点关系都没有

我的想法

理论复杂度\(O(n\sqrt n)\),其实从对拍来看,当\(n\le10^5\)时,速度和洛谷上\(O(n)\)标程相当.

几个数组:

int a[nn];//输入的数组
int p[nn];//p[i]表示数字i在a中的下标
int nxt[nn];//优化,nxt[i]=j表示区间(i,j)中所有vis值均为true
bool vis[nn];//若vis[i]==true,则a[i]已经加入到目标队列

不难想到,\(i\)\(n\)往1找,若\(vis_{p_i}==false\),从\(p_i\)开始找第一个\(j\),使\(vis_j==false\)(若没找到,证明\(i\)是原龙珠序列的最后一个数),更新\(vis\),输出\(i\),\(a_j\)

时间复杂度可以去到\(O(n^2)\),需要优化

可以发现,不必要的时间用在找\(j\),因此,我们从这里入手

借助分块思想,如果找\(j\)的循环次数超过\(\sqrt n\),我们就更新\(nxt\)数组:

			int las = n + 1;
			for(int j = n ; j > 0 ; j--)
				if(!vis[j])
					nxt[j] = las , las = j;

这样,找\(j\)的循环次数基本不超过\(\sqrt n\),更新\(nxt\)数组的次数也不超过\(\sqrt n\),每次更新需要\(O(n)\)的时间

所以,时间复杂度为\(O(n\sqrt n)\),可通过

来自洛谷某题解

新鲜的题!!!

先用链表存储每一个编号的数前一个和后一个数的编号,最后由大到小枚举一遍,将没有用过的点连上后一个一起输出(注意它能输出,当且仅当它后面有数)

输出以后记得把它前一个数和它后面的后面的数连上

然后,就可以上代码了

#include <cstdio>

int n,a[100001],k[100001],x[100001],i;

int main()
{
 scanf("%d",&n);
 for(i=1;i<=n;++i){
     scanf("%d",&a[i]);
     k[a[i-1]]=a[i];
     x[a[i]]=a[i-1];}
 for(i=n;i>=1;--i)
     if(k[i])printf("%d %d ",i,k[i]),k[x[i]]=k[k[i]],x[k[x[i]]]=x[i],k[k[i]]=0;
 return 0;
}

代码

AC代码

#include <iostream>
#include <cstdio>
#define nn 100010
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}
int n;
int a[nn];
int p[nn];
int nxt[nn];
bool vis[nn];
int main() {
	n = read();
	for(int i = 1 ; i <= n ; i++)
		p[a[i] = read()] = i;
	for(int i = 1 ; i <= n + 1 ; i++)
		nxt[i] = i + 1;
		
	for(int i = n ; i > 0 ; i--) {
		if(vis[p[i]])	continue;
		int curnxt = nxt[p[i]];
		int cnt = 0;
		while(vis[curnxt] && curnxt <= n)
			curnxt = nxt[curnxt] , ++cnt;
		if(curnxt == n + 1)
			continue;
		printf("%d %d " , i , a[curnxt]);
		vis[curnxt] = vis[p[i]] = true;
		
		if(cnt * cnt >= n) {
			int las = n + 1;
			for(int j = n ; j > 0 ; j--)
				if(!vis[j])
					nxt[j] = las , las = j;
		}
	}
	return 0;
}

随机数据

#include <bits/stdc++.h>
using namespace std;
int a[100010];
int main() {
	unsigned seed;
	cin >> seed;
	seed *= time(0);
	srand(seed);
	
	int n = (long long)seed * rand() * rand() % 99999 + 1;
	if(n & 1)++n;
	cout << n << endl;
	for(int i = 1 ; i <= n ; i++)
		a[i] = i;
	random_shuffle(a + 1 , a + n + 1);
	for(int i = 1 ; i <= n ; i++)
		printf("%d " , a[i]);
	return 0;
}

D. 【例题4】工作安排

题目

传送门

思路

贪心+大根堆

我们把工作截止时间从大到小排序,设当前时间为\(d_i\)(注意此时第\(i\)项工作已经截止),把截止时间为\(d_i\)的工作全部放进堆里,设\(j<i\)\(d_j\neq d_i\),\(j\)取最大值,我们若堆不为空,从堆中取出\(d_j-d_i\)个工作,把它们的\(P\)累加到答案中

输出答案即可

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define nn 100010
#define ll long long
using namespace std;
int read() {
	int re = 0;
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	while(c >= '0' && c <= '9')
		re = (re << 1) + (re << 3) + c - '0',
		c = getchar();
	return re;
}
struct Heap {
	int siz;
	int a[nn * 2] , b[nn * 2];
	bool ty;
	inline void swap_(int x , int y) {
		int tmp;
		tmp = a[x] ; a[x] = a[y] ; a[y] = tmp;
		tmp = b[x] ; b[x] = b[y] ; b[y] = tmp;
	}
	void clear() {
		siz = 0 , ty = 0;
		memset(a , 0 , sizeof(a));
	}
	inline void push(int dat , int dat2) {
		a[++siz] = dat;
		b[siz] = dat2;
		int p = siz;
		while(p > 1 && (!(a[p >> 1] < a[p]) ^ ty))
			swap_(p >> 1 , p) , p >>= 1;
	}
	inline void pop() {
		swap_(1 , siz);
		--siz;
		int p = 1 , tmp;
		while(p * 2 <= siz && (!(a[p] < a[tmp = ( p * 2 + 1 > siz ? p * 2 : (a[p * 2] < a[p * 2 + 1] ^ ty ? p * 2 : p * 2 + 1) ) ] ) ^ ty))
			swap_(tmp , p) , p = tmp;
	}
	inline int top() {
		return siz == 0 ? 0 : a[1];
	}
	inline bool empty() {
		return siz == 0;
	}
} h;
struct node {
	int d , p;
} wk[nn];
bool cmp(node a , node b) {
	return a.d > b.d;
};
int n;
signed main() {
//	freopen("P2949_2.in" , "r" , stdin);
	h.clear();
	h.ty = 1;

	n = read();
	for(int i = 1 ; i <= n ; i++)
		wk[i].d = read() , wk[i].p = read();
	++n;
	wk[n].d = wk[n].p = 0;
	sort(wk + 1 , wk + n + 1 , cmp);
	
	ll ans = 0;
	h.push(wk[1].p , wk[1].d);
	
	int i;
	for(i = 2 ; i <= n && wk[i].d == wk[i - 1].d ; i++)
		h.push(wk[i].p , wk[i].d);
	while(i <= n) {
		int num = wk[i - 1].d - wk[i].d;
		while(num-- && !h.empty()) {
			ans += (ll)h.top();
			h.pop();
		}
		h.push(wk[i].p , wk[i].d);
		for(++i ; i <= n && wk[i].d == wk[i - 1].d ; i++)
			h.push(wk[i].p , wk[i].d);
	}
	cout << ans;
	return 0;
}
posted @ 2021-04-08 19:11  追梦人1024  阅读(74)  评论(0编辑  收藏  举报