NOI2020D2T2超现实树题解

题意

N N N 组数据,每组数据给定 m m m 棵二叉树,其中每棵树有 n n n 个节点。

定义一次操作为将 m m m 棵二叉树中任意一棵树的叶节点换成任意一棵二叉树,问进行若干次这样的操作后形成的树的集合是否为几乎完备的。

几乎完备:若一个集合中只不包括有限棵二叉树,则称它为几乎完备的。

N ≤ 40 N \le 40 N40

∑ n , ∑ m ≤ 1 0 6 \sum n,\sum m \le 10^6 n,m106

题解

神仙题!

读完题目,首先要去思考怎样的树是对集合成为几乎完备的有贡献。逐层考虑每棵二叉树,会发现每层的每个非叶子的节点大体可以分为只有左子树、只有右子树、左右子树都有三种情况。

要想这个集合是几乎完备的,必须保证每层的每个节点均可经过以上操作变成三种情况中任意一种,否则如果不包含某种情况,则由那种情况长出的树也都不会被包含(能长出无限棵)。

并且,分层考虑时给定的树只有在包含仅一个节点一种情况时才是对答案有贡献的,否则它无法生长成仅包含其中一种情况的树,也可以被别的有贡献的树所替代。它能被替代,又替代不了别人,像极了那啥

思考到这步时大家可能会有一个问题:不满足上述条件的树能否经过互补对答案产生贡献?

事实上是不行的。因为不管有多少棵包含多种情况的树,只包含一种情况的树都无法被生成,且只包含一种情况的树是必要的。依据这种情况,我们所能做的只有补上这些只包含一种情况的树,但发现所有有多种情况的树都能被一种情况的树所生成,所以不满足上述条件的树无法互补以产生贡献。

于是我们从第一层开始考虑,依次看到最高一层。画一画所有这种符合要求的树会发现在每一层均有意义的树恰恰有一个特点:每个节点左子树与右子树大小的 min ⁡ \min min 值小于等于 1 1 1。我们称这种树为链树。

到这一步时,我们已经可以处理出所有这种树,并通过 dfs 判断每层是否包含所有情况。于是,我们为了方便将每个节点分为四类:

  1. 只包含左子树的。
  2. 只包含右子树的。
  3. 包含左右子树且左子树为叶节点的。
  4. 包含左右子树且右子树为叶结点的

这种分类恰恰包括了所有的情况,我们可以很方便地将它们合并到一棵四叉树上,如果某个节点不包含所有的四种情况且不是叶节点,那么这个集合不可能是几乎完备的。

注意:类型为 3 的节点同时也是类型为 4 的节点,它们的区别仅仅是递归的方向。

Code

代码还是有挺多细节的,且有些操作并不是那么容易实现,一定要仔细思考,在理解的基础上做题。

#include<bits/stdc++.h>
using namespace std;
int T,m,n,l[2000005],r[2000005],f[2000005],tot,s[2000005],t[2000005][4],v[2000005];//l,r数组代表给定的树,t代表四叉树 
inline int read(){
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}
bool dfs1(int x){//算子树大小以判断链树 
	if(!l[x]&&!r[x]){
		s[x]=1;return 1;
	}
	if(l[x])if(!dfs1(l[x]))return 0;
	if(r[x])if(!dfs1(r[x]))return 0;
	s[x]=s[l[x]]+s[r[x]]+1;
	if(s[l[x]]>1&&s[r[x]]>1)return 0;
	return 1;
}
int New(int &x){//动态开点 
	if(!x)x=++tot;
	return x;
}
void dfs(int x,int p){//x表示四叉树上的节点编号,p表示要合并的链树上的节点编号 
	int s1=s[l[p]],s2=s[r[p]];
	if(s1==0&&s2==0){
		v[x]=1;return;//判断叶节点 
	}
	if(s1>=1&&s2==0)dfs(New(t[x][0]),l[p]);
	if(s2>=1&&s1==0)dfs(New(t[x][1]),r[p]);
	if(s1>=1&&s2==1)dfs(New(t[x][2]),l[p]);
	if(s2>=1&&s1==1)dfs(New(t[x][3]),r[p]);
}
bool check(int x){
	if(v[x])return 1;
	if(!t[x][0]||!t[x][1]||!t[x][2]||!t[x][3])return 0;
	if(!check(t[x][0])||!check(t[x][1])||!check(t[x][2])||!check(t[x][3]))return 0;
	return 1;
}
int main(){
	cin>>T;
	while(T--){
		m=read();
		bool f=0;
		for(int i=0;i<=tot;i++){
			t[i][0]=t[i][1]=t[i][2]=t[i][3]=v[i]=0;
		}
		tot=1;
		for(int i=1;i<=m;i++){
			n=read();
			for(int j=1;j<=n;j++)l[i]=r[i]=0;
			if(n==1){//特判只有根节点的情况 
				f=1;
			}
			for(int j=1;j<=n;j++){
				l[j]=read(),r[j]=read();
			}
			if(!dfs1(1))continue;
			dfs(1,1);
		}
		if(!f&&!check(1))cout<<"No"<<endl;
		else cout<<"Almost Complete"<<endl;
	}
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值