题目
算法思路
- 第一眼看完题目,因为树是可以旋转的,第一反应一般都是全部找到所有最小值,再全部找到所有最大值,然后以其为根进行判断,后面发现可能有多个最大值和最小值,构造一组数据,有5k个最大值和5k个最小值,可以把这个错误的算法给卡掉,后面根据堆的性质,判断连边为2的节点,才可能是根,这样就优化了算法
- 接下来只需要以度为2的节点为根,判断他是否是一个合法的堆即可以,因为复杂度问题,判断是否是大小根堆的复杂度,明显小于判断是否是完美二叉树的,所以我们先判断是否是大小根堆
- 判断大小根堆很easy的,这边就不说了
- 判断是否是完美二叉树,可以构造出一组数据,如下
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
- 所以,如果只判断底层的话,再加上这棵树的左右节点可以互换,很明显是一种错误的思路,所以我们不止需要判断最底层,我们需要判断所有层数
- 首先先判断一下所有层的节点数是否合法,此处根节点为第0层,
num[i]
代表第i层的节点数量,如果num[i]!=(1<<i)
则不合法 num_leaf[i]
代表以 i i i节点的子树中,所包含的叶子结点数量- 维护18个大顶堆(下标到17)( 2 16 ≤ 2^{16}\leq 216≤ n的最大值 ≤ 2 17 \leq 2^{17} ≤217)
- 令
x=1<<(d-i)
,nsum
代表整棵树的叶子结点数,一层用一个堆表示,对于节点 i i i将num_leaf[i]
放入第 h [ i ] h[i] h[i]个堆中,其中 h [ i ] h[i] h[i]代表第 i i i个节点在第几层 - 理论上同一层,应该只有若干个x值,和至多一个的非x值(即
nsum%x
) - 可以理解为把叶子节点数量,作为一个标记数x ,如果它是一个合法的完美二叉树,对于每一层,最多仅由一个非x值
- 我们借助18个大顶堆或者18个map(因为同一层最多只有2种值),即可判断它是否是完美二叉树
- 至于如何判断,大顶堆的做法,如果
top()=x
则直接pop
,否则只有一种情况,就是top()=nsum%x
并且heap.size()==1
- 这边
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);
}
}
}