(是否是合法堆)lab9_Is_a_heap

题目

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

算法思路

  1. 第一眼看完题目,因为树是可以旋转的,第一反应一般都是全部找到所有最小值,再全部找到所有最大值,然后以其为根进行判断,后面发现可能有多个最大值和最小值,构造一组数据,有5k个最大值和5k个最小值,可以把这个错误的算法给卡掉,后面根据堆的性质判断连边为2的节点,才可能是根,这样就优化了算法
  2. 接下来只需要以度为2的节点为根,判断他是否是一个合法的堆即可以,因为复杂度问题,判断是否是大小根堆的复杂度,明显小于判断是否是完美二叉树的,所以我们先判断是否是大小根堆
  3. 判断大小根堆很easy的,这边就不说了
  4. 判断是否是完美二叉树,可以构造出一组数据,如下

1
12
1 2 3 4 5 6 7 8 9 10 11 12
1 2
1 3
2 4
2 5
3 6
3 7
4 8
4 9
6 10
7 11
7 12

  1. 所以,如果只判断底层的话,再加上这棵树的左右节点可以互换,很明显是一种错误的思路,所以我们不止需要判断最底层,我们需要判断所有层数
  2. 首先先判断一下所有层的节点数是否合法,此处根节点为第0层,num[i]代表第i层的节点数量,如果num[i]!=(1<<i)则不合法
  3. num_leaf[i]代表以 i i i节点的子树中,所包含的叶子结点数量
  4. 维护18个大顶堆(下标到17)( 2 16 ≤ 2^{16}\leq 216 n的最大值 ≤ 2 17 \leq 2^{17} 217
  5. x=1<<(d-i)nsum代表整棵树的叶子结点数,一层用一个堆表示,对于节点 i i inum_leaf[i]放入第 h [ i ] h[i] h[i]个堆中,其中 h [ i ] h[i] h[i]代表第 i i i个节点在第几层
  6. 理论上同一层,应该只有若干个x值,和至多一个的非x值(即nsum%x)
  7. 可以理解为把叶子节点数量,作为一个标记数x ,如果它是一个合法的完美二叉树,对于每一层最多仅由一个非x值
  8. 我们借助18个大顶堆或者18个map(因为同一层最多只有2种值),即可判断它是否是完美二叉树
  9. 至于如何判断,大顶堆的做法,如果top()=x则直接pop,否则只有一种情况,就是top()=nsum%x并且heap.size()==1
  10. 这边map里面储存两个值比这边快很多,也很好写,这边就不赘述了。

代码实现

/*
判断给定的一棵树是堆
1.完美二叉树
2.小顶堆 or 大顶堆
*/
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
#define el '\n'
#define cl putchar('\n')
#define eb push_back
#define fir first
#define sec second

typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> vci;
typedef map<int,int> mii;
typedef mii::iterator mii_it;

const int N=1e5+10,M=1e3+10;

int T,n,m,x,y,k;
int a[N];
vci v[N],rt;
//rt中储存的是边数大于2的,节点数
bool flag=0;
vci mx_id,mi_id;
int num[N],h[N],num_l[N];
bool vis[N];
int d;
bool check_mi(int u,int fa) {//判断是否是小根堆
	for(int i=0; i<v[u].size(); i++) {
		int t=v[u][i];
		if(t==fa)continue;
		if(a[t]<a[u])return 0;
		if(!check_mi(t,u))return 0;
	}
	return 1;
}

bool check_mx(int u,int fa) {//判断是否是大根堆
	for(int i=0; i<v[u].size(); i++) {
		int t=v[u][i];
		if(t==fa)continue;
		if(a[t]>a[u])return 0;
		if(!check_mx(t,u))return 0;
	}
	return 1;
}
bool check_num() {
	for(int i=1; i<=n; i++) {
		if(v[i].size()>3)return 0;//超过三条边,必然不是二叉树
		if(v[i].size()==2) {
			rt.eb(i);
		}
	}
	if(rt.size()>2)return 0;//一个堆中sz==2的最多只有两个
	return 1;
}
void get_leaf(int u) {//获取以当前节点为根的树中,第d层(最下面一层)的叶子结点数量
	num_l[u]=0;//多组样例,初值
	if(h[u]==d) {//叶节点
		num_l[u]=1;
		return ;
	}
	for(int i=0; i<v[u].size(); i++) {
		int t=v[u][i];
		if(!vis[t]) {
			vis[t]=1;
			get_leaf(t);
			num_l[u]+=num_l[t];//加上他的子树中的num_l[]
		}
	}
}
bool check_per(int u) {//判断是否是完美二叉树
	if(n<=2)return 1;//两个节点以下的,必然是
	queue<int> q;
	q.push(u);
	memset(vis,0,sizeof vis);
	memset(h,0,sizeof h);
	memset(num,0,sizeof num);
	vis[u]=1;
	h[u]=0;
	num[0]=1;
	while(!q.empty()) {//bfs搜索获取节点深度
		int t=q.front();
		q.pop();
		for(int i=0; i<v[t].size(); i++) {
			int w=v[t][i];
			if(!vis[w]) {
				vis[w]=1;
				q.push(w);
				h[w]=h[t]+1;
				num[h[w]]++;//当前层数的节点数量
			}
		}
	}
	d=log(n)/log(2);//最大深度
	for(int i=1; i<d; i++) {
		if(num[i]!=(1<<i))return 0;//第i层具有(2^i)个节点
	}
	memset(vis,0,sizeof vis);
	vis[u]=1;
	get_leaf(u);//获取以当前节点为根的树中,第d层(最下面一层)的叶子结点数量
	priority_queue<int> heap[18];//第i个大顶堆,储存深度为i的节点的num_l[]
	for(int i=1; i<=n; i++) {
		if(num_l[i]){
			heap[h[i]].push(num_l[i]);
		}
	}
	int nsum=n-(1<<d)+1;//前d-1层有(1<<d)-1个节点,用n减去,即得到最后一层的节点数量,即num[d]…………i m sb
	for(int i=d-1;i>=0;i--){
		x=1<<(d-i);
		while(!heap[i].empty()){//第i个堆中,理论上应该只有若干个x值,和至多一个的非x值,即nsum%x
			if(x==heap[i].top()){
				heap[i].pop();
			}
			else {
				if(heap[i].size()==1&&heap[i].top()==nsum%x)heap[i].pop();
				else return 0;
			}
		}
	}
	return 1;
}
void check() {//检查是否是一个合法的堆
	for(int i=0; i<rt.size(); i++) {
		if(check_mi(rt[i],0)||check_mx(rt[i],0)) {//检查是否是小根堆,或者大根堆
			if(check_per(rt[i])) {//检查是否是完美二叉树
				flag=1;
				return ;
			}
		}
	}
}
void Pr(int t,bool f) {//输出结果
	if(f)cout<<"Case #"<<t<<": YES"<<el;
	else cout<<"Case #"<<t<<": NO"<<el;
}
int main() {
	cin.tie(0);
	cout.tie(0);
    cin.sync_with_stdio(false);
	cin>>T;
	for(int p=1; p<=T; p++) {//第p组数据
		cin>>n;
		rt.clear();//初值
		for(int i=1; i<=n; i++)v[i].clear();//初值
		for(int i=1; i<=n; i++) {//输入权值
			cin>>a[i];
		}
		for(int i=1; i<=n-1; i++) {//读入树形结构
			cin>>x>>y;
			v[x].eb(y);
			v[y].eb(x);
		}
		if(!check_num()) {//检查节点的连边数量,是否合法
			Pr(p,0);
		} else {
			flag=0;
			check();
			Pr(p,flag);
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值