传送门:智乃的“黑红树”
标签:构造
题目大意
智乃有一颗"黑红树",“黑红树"是这样定义的:
1、“黑红树"中黑色节点的直接孩子为红色节点、红色节点的直接孩子为黑色节点。
2、“黑红树"是一颗以1号节点为根节点的二叉树,且根节点的颜色为黑色。
3、对于"黑红树"上任意一个节点,要么同时存在两个子节点,要么为叶节点不存在孩子。
现在智乃想要构造一颗根节点为1,树尺寸为N,节点编号从1到N的"黑红树”,该"黑红树”“恰好有a个黑点和b个红点,请你帮助她构造任意满足条件的"黑红树”,或者告诉她不存在任何满足条件的"黑红树”。
输入:T组数据,每组数据两个正整数n,m,分别代表黑红树中黑节点和红节点的数量。
输出:存在这样的黑红树则输出”Yes“,否则输出”No“。
算法分析
- 首先我们知道红黑树是什么,但智乃的黑红树略有不同。他要求二叉树中的节点要么有两个节点要么有两个孩子要么为叶子节点。那么显然n必须为奇数,m必须为偶数,因为第一层的根节点为黑节点。我们自然会想到一种最简单的构造方法,就是奇数层排满黑节点,偶数层排满红节点,直到最后一层。
- 这里引入完全二叉树的概念。大家都知道满二叉树,而完全二叉树就是除最后一层外为满二叉树,且最后一层的节点全部靠左侧。如果按照我们刚才说的构造方法,就会近似得到一棵完全二叉树,如果造不出来就说明无解。但这种方法是不是最优的还有待考虑。我们假设n=3,m=4,就会发现第一到四层的节点数量分别为:1,2,2,2,显然不是一棵完全二叉树,那么就证伪了。
- 虽然最简单的方法行不通,但我们也发现了一个关键的信息:“黑红树”除去最后一层必为完全二叉树。也就是说只要稍微修改一下策略就能通过本题。构造完全二叉树时我们会想到编号为i的节点的子节点编号一定为2i和2i+1,因为只有排满了上一层才会有下一层。但是在本题中没有此规律,所以我们要采用时间戳dfn来为节点标号。
- 定义一个f数组,初始化为-1,当为0时表示当前节点在奇数层,当为1时表示当前节点在偶数层。最初令f[1]=0,然后从1到n+m遍历i,如果当前节点f值为-1说明它不是根节点且没有父节点,直接输出No。如果当前节点在奇数层且m大于0,就可以在它下面挂两个红节点,并将这两个节点的f值更新为1。如果当前节点在偶数层且n大于0,就可以在它下面挂两个黑节点,并将这两个节点的f值更新为0。该算法总体时间复杂度为O(n)。
代码实现
#include <iostream>
using namespace std;
int dfn;
int l[200005],r[200005],f[200005];
#define lowbit(x) (x)&(-x)
int main(){
long long dx,dy,p,q,i,j,x,y,x1,y1,n,m,T,sum,mx;
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>T;
while(T--){
cin>>n>>m;
sum=n+m;
if(n%2==0||m%2==1){cout<<"No\n";continue;}
for(i=1;i<=n+m+1;i++)l[i]=r[i]=f[i]=-1;
f[1]=0;dfn=1;n--;
for(i=1;i<=sum;i++){
if(f[i]==-1){
cout<<"No\n";
goto abc;
}
if(f[i]==0&&m){
l[i]=++dfn;f[dfn]=1;
r[i]=++dfn;f[dfn]=1;
m-=2;
}
if(f[i]==1&&n){
l[i]=++dfn;f[dfn]=0;
r[i]=++dfn;f[dfn]=0;
n-=2;
}
}
cout<<"Yes\n";
for(i=1;i<=sum;i++)
cout<<l[i]<<' '<<r[i]<<'\n';
abc:;
}
return 0;
}