[NOI2020] 超现实树
XI.[NOI2020] 超现实树
Observation 1.答案为
No
当且仅当有至少一棵深度为无限的树无法被生成。
这是显然的。
Observation 2.答案为
No
当且仅当存在至少一棵深度为 的树无法被生成。
因为初始森林中不存在任何深度 的节点,所以任何深度 的树都可以被还原成一棵深度 的树。
Observation 3.对于某一深度 ,存在一组元素全部为深度为 的树的集合 ,使得所有深度为 的树都可以由此集合中的树长出,且集合内的树两两不存在长出关系。
我们可以由深度为 的树的全集开始,每次删掉一棵可以由集合内其它树长出的树得到。明显,若一棵树 可以由另一棵树 长出,则必有 ,也即 可以完美替代 。不断删下去,最终一定可以得到上述集合 。记 ,表示深度为 的树的基底。
Observation 4.对于每个 , 唯一。
套用上述删树的过程就能得出。因为在上述算法中,删掉一棵树并不影响 ,因而任意时刻,原本要被删去的树不会变得不需删去。
Observation 5.对于每个 , 由全体链树构成,其中链树的定义是所有由一条长度为 的链,以及往上面添加的一些叶子节点构成的树。
Proof 5.1.链树集合能生成全体深度为 的树的集合。
考虑任一棵深度为 的树。其定具有至少一条长度为 的链,我们找到它。考虑构造出一棵能生成它的链树。我们首先让链树拥有这条链。然后,考虑原树中此链上的每一个节点。其必有一个儿子是链上节点。若其有另一个儿子,就让链树也拥有这个儿子,则该儿子的子树可以由链树上此儿子生长出;否则,即其没有另一个儿子,则链树也没有另一个儿子。依照此方式构造出的树必定是链树,且能够生成该深度为 的树。
Proof 5.2.链树集合中的树两两不存在长出关系。
考虑反证。假设存在两颗链树 ,且 可由 长出。
Lemma 1.任意深度为 的链树,其至少有一个深度为 的节点,至多有两个。若是后者,此两个节点是的父亲是同一个深度为 的节点。
这是显然的,因为任意深度为 的节点必有深度为 的父亲;而深度为 的点,要么是不能有儿子的叶子,要么是链节点。
依据 Lemma 1, 各拥有两个非空集合 ,意味着各自深度为 的节点集合。显然,必有 ,因为生长关系不可能删除节点。这又分为三种情形,一是二者皆包含 个节点,二是后者包含两个兄弟节点,且前者包含其一,三是二者皆包含 个节点。
若是第二种情形,显然 中那个兄弟节点不能由任何节点长出(它们的父亲并非能够生长的叶子节点),故此种情形不成立。
现在考虑第一种情形。它们都有着唯一的深度为 的节点,这等价于有着相同的链。
而若是第三种情形,我们可以钦定一个节点作为链的终点,另一个节点作为链上长出的一个叶子,然后与第一种情形一并计算。
现在,考虑链上一个节点。在 和 中,它都会存在一个儿子作为链上后继。考虑其另一个儿子。显然,在 中出现而在 中不出现的情形是不可能存在的;在 中不出现而在 中出现也是不可能存在的,因为其不能由父亲节点长出。故它们要么同时存在某个节点,要么同时不存在,也即 。
Observation 5 证毕。
Observation 6. 任意非链树生成不了任意链树。
因为生成操作删不了点。
Observation 7. 可以只保留一切链树,舍去所有非链树。
因为非链树不可能生成链树,故保留了也没用。
Observation 8. 可以合并链树。
考虑链树上每个节点。其子节点有四种相互无交的可能:仅有左儿子、仅有右儿子、左儿子是链儿子而右儿子是叶子、右儿子是链儿子而左儿子是叶子(若出现 Lemma 1 中提到的后一种情形,可以同时计入最后两种情形)。显然,对于每个节点,可以记录其有无在某棵链树中,子树情况是上述四种情形之一,并分别拿四种情形下的链儿子作为其四个儿子。这样便压缩了链树。
Observation 9. 压缩后的链树中,每个点代表一棵子树;若子树中仅有有限棵树无法被生成,必是如下两种情形之一:
- 该点在某一棵链树中是叶子节点。
- 该点拥有全部四个儿子,且四个儿子代表的子树中都仅有有限棵树无法被生成。
四个儿子分别对应了四种情形的子树:没有右儿子、没有左儿子、有右儿子、有左儿子。四种情形都有着有限棵的无法生成的集合等价于总共有着有限棵无法被生成。
于是直接 dfs 整棵四叉树即可。时间复杂度 。
代码:
#include<bits/stdc++.h>
using namespace std;
int T,m,ch[2001000][2],sz[2001000],n,CH[2001000][4],rt;
bool ok[2001000];
bool checkchain(int x){
sz[x]=1;
for(int i=0;i<2;i++){
if(!ch[x][i])continue;
if(!checkchain(ch[x][i]))return false;
sz[x]+=sz[ch[x][i]];
}
return !(ch[x][0]&&ch[x][1]&&sz[ch[x][0]]!=1&&sz[ch[x][1]]!=1);
}
void addtree(int &x,int y){
if(!x)ok[x=++n]=false;
if(!ch[y][0]&&!ch[y][1]){ok[x]=true;return;}
if(ch[y][0]&&!ch[y][1]){addtree(CH[x][0],ch[y][0]);return;}
if(!ch[y][0]&&ch[y][1]){addtree(CH[x][1],ch[y][1]);return;}
if(sz[ch[y][1]]==1)addtree(CH[x][2],ch[y][0]);
if(sz[ch[y][0]]==1)addtree(CH[x][3],ch[y][1]);
}
bool grow(int x){
if(ok[x])return true;
if(!x)return false;
bool ret=true;
for(int i=0;i<4;i++)ret&=grow(CH[x][i]);
return ret;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&m),rt=n=0;
for(int i=1,N;i<=m;i++){
scanf("%d",&N);
for(int j=1;j<=N;j++)scanf("%d%d",&ch[j][0],&ch[j][1]);
if(!checkchain(1))continue;
addtree(rt,1);
}
puts(grow(rt)?"Almost Complete":"No");
for(int i=1;i<=n;i++)for(int j=0;j<4;j++)CH[i][j]=0;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?