Solution -「NOI 2020」「洛谷 P6776」超现实树
Link.
对于非空二叉树 ,定义 为所有能通过若干次“替换 的某个叶子为任意非空二叉树”的操作得到的二叉树集合;对于非空二叉树集合 ,定义 。多次询问,每次给定一个 ,询问是否仅有有限个二叉树不在 中。二叉树判等时区分左右儿子,点无编号。
设单棵树的结点数为 ,则 。
这 tm 才叫简要题意。
谨以本题解表达去年做题的兔子对题面的赞美。
:我们先来研究一个几乎完备的 吧,在这样的 中,会不会有些树不可起到使 完备的作用呢?
:嗯,每片叶子都可以生长出任何二叉树,那么,起到决定性作用的是否叶子们的深度?
:那就先控制深度信息!我发现书上也有在这个思路下衍生的定义。
定义:二叉树集合 为高度为 的全体二叉树集的基,定义为:
:简而言之,这个 包含了所有高度为 且不能被其他高度为 的树“生长”出来。很显然, 包含了所有高度为 的二叉树!
:继续深入的话……说不定 里的 也有特殊性质呢。
:(手玩片刻)呀!这样的 看上去都很“瘦小”,具体地,如果结点 同时拥有左右儿子,那么必然有至少一个儿子是叶子。嗯!这是能够证明的结论。
证明:记 为真时, 满足上述性质,否则则不满足。设 ,我们希望证明
对于 ,取出 中一定存在的自根而下长为 的树链 ,这样的链是单独的一条或者在末尾分叉的两条。我们可以断言,若有 ,则 必然满足 。而对于其余叶子,由于它们的父亲都在 的点集中且不是叶子,所以它们也必然在 的点集中。最终,发现 ,故命题成立。
另一边,,仍使用反证法。对于不满足条件的 ,取出一个左右儿子存在且均不是叶子的结点 ,它必然有一个儿子 满足删去 子树后 。则可将 子树“逆向生长”为单点 ,此时得到的 满足 且 ,所以 ,假设矛盾,原命题成立。
综上,命题得证。
:离成功更近一步!把这个强大的结论用于题目本身,是不是说,如果 不满足上述条件,就能从 中剔除 而不改变 的完备性?……没错!如果 是这样一棵树,且不存在 使得 ,如果存在某棵树只能由 生长出来,我们一定能找到这棵树中一个左右儿子均存在且不是叶子的结点 ,将它的一个儿子替换为单点。那么,这棵树的 集合与 交集为空!
:接下来就只需要直面问题,尝试判断 是否几乎完备。我们从一棵“希望与 匹配的树”的视角出发,设当前结点为 ,如果 子树能够匹配上:
- 需要 , 在此位置上的结点是叶子,这强于以下所有条件。
- 只有左儿子,需要一个只有左儿子的 ;
- 只有右儿子,需要一个只有右儿子的 ;
- 左儿子是叶子,需要同样情况的 ;
- 右儿子是叶子,需要同样情况的 ;
- 左右儿子都是叶子,3. 4. 任意。
我们定义 是一棵四叉树,它是对 的等价概括。我们可以每个 合并入其中,依照 1. 2. 3. 4. 四种情况划分儿子编号。最后,定义 该四叉树中 子树的完备性,有
最终, 即为答案。复杂度 !
:不过我发现一个问题欸,为什么题目描述比题解还长呢?
/* Clearink */
#include <cstdio>
#include <cassert>
#define rep( i, l, r ) for ( int i = l, rep##i = r; i <= rep##i; ++i )
#define per( i, r, l ) for ( int i = r, per##i = l; i >= per##i; --i )
inline char fgc() {
static char buf[1 << 17], *p = buf, *q = buf;
return p == q && ( q = buf + fread( p = buf, 1, 1 << 17, stdin ), p == q )
? EOF : *p++;
}
inline int rint() {
int x = 0; char s = fgc();
for ( ; s < '0' || '9' < s; s = fgc() );
for ( ; '0' <= s && s <= '9'; s = fgc() ) x = x * 10 + ( s ^ '0' );
return x;
}
const int MAXN = 2e6;
int m;
struct BinaryTree {
int node, ch[MAXN + 5][2];
inline void clear() {
rep ( i, 1, node ) ch[i][0] = ch[i][1] = 0;
node = 0;
}
inline void read() {
clear(), node = rint();
rep ( i, 1, node ) ch[i][0] = rint(), ch[i][1] = rint();
}
inline int type( const int u ) const {
/*
-1: illegal node (not a basic tree).
0: left only.
1: right only.
2: left chain and right leaf.
3: right chain and left leaf.
4: 2&3.
*/
int lc = ch[u][0], rc = ch[u][1];
assert( lc || rc );
if ( !rc ) return 0;
if ( !lc ) return 1;
bool lf = !ch[lc][0] && !ch[lc][1], rf = !ch[rc][0] && !ch[rc][1];
if ( lf && rf ) return 4;
if ( rf ) return 2;
if ( lf ) return 3;
return -1;
}
inline bool check( const int u ) {
if ( !u || ( !ch[u][0] && !ch[u][1] ) ) return true;
return ~type( u ) && check( ch[u][0] ) && check( ch[u][1] );
}
} binT;
struct SynopsisTree {
int node, ch[MAXN + 5][4];
bool tag[MAXN + 5];
inline void clear() {
rep ( i, 1, node ) {
ch[i][0] = ch[i][1] = ch[i][2] = ch[i][3] = 0,
tag[i] = false;
}
node = 1;
}
inline void storage( const BinaryTree& T, int& u, const int v ) {
if ( !v ) return ;
if ( !u ) u = ++node;
if ( tag[u] ) return ;
if ( !T.ch[v][0] && !T.ch[v][1] ) return void( tag[u] = true );
int f = T.type( v );
if ( !f ) storage( T, ch[u][0], T.ch[v][0] );
else if ( f == 1 ) storage( T, ch[u][1], T.ch[v][1] );
else {
if ( f == 2 || f == 4 ) storage( T, ch[u][2], T.ch[v][0] );
if ( f == 3 || f == 4 ) storage( T, ch[u][3], T.ch[v][1] );
}
}
inline bool complete( const int u ) {
if ( !u || tag[u] ) return !!u;
return complete( ch[u][0] ) && complete( ch[u][1] )
&& complete( ch[u][2] ) && complete( ch[u][3] );
}
} synT;
int main() {
for ( int T = rint(), root = 1; T--; ) {
synT.clear(), m = rint();
rep ( i, 1, m ) {
binT.read();
if ( binT.check( 1 ) ) synT.storage( binT, root, 1 );
}
puts( synT.complete( 1 ) ? "Almost Complete" : "No" );
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现