[NOI2020] 超现实树

链接

为什么我没有看到第5,6个样例?

题目大意

定义一颗二叉树 \(S\) 将若干个叶子节点替换为任意二叉树后的树与 \(T\) 同构,称为 \(S\) 包含 \(T\)

现在给定 \(n\) 颗树 \(S_i\),问对于所有二叉树,是否只有有限个二叉树没有任意一个 \(S_i\) 包含。

题解

结论题。

首先我们可以把题目看成:问能否构造一颗高度无限的二叉树,使其不被任意一个 \(S_i\) 包含。

考虑包含的性质,我们可以再次转换问题,即:问能否构造一颗无限长的二叉树,使对于任意 \(S_i\) ,存在一个非叶子节点的儿子信息不同(有/无左儿子,有/无右儿子)。

再考虑一个很显然的性质:如果有两棵二叉树 \(S,T\)\(T\) 根的左子树不被 \(S\) 根的左子树包含,那么 \(T\) 一定不被 \(S\) 包含。

首先,如果存在 \(S_i\) 是一个节点,那么一定不存在。

否则贪心地从根考虑构造:可以发现,如果根的左子树中存在一种树,不被任何 \(S_i\) 根的左子树包含,那么就存在一颗无限长的二叉树,使其不被任意一个 \(S_i\) 包含。

即我们希望在左子树中构造一种树,使其不被当前集合中任何 \(S_i\) 包含。如果成功,由于上述性质,意味着右子树无论怎么构造都不会被包含。

很明显,假如这种树存在,其中一定包含只有根结点的树。所以构造只有根结点的树(即叶子)一定不会劣。

反之,如果左子树中不存在这样的一种树,即当前的 \(S_i\) 中存在一棵树,其根的左儿子是叶子。那么这样作为构造方就有了一种选择:选/不选左儿子。可以发现第一条对应的集合变成了“根有左儿子且左儿子是叶子”,第二条对应的集合变成了“根没有左儿子”。

对右儿子同理。可以发现,作为构造方,我们现在有4种选择:

  1. 空出左子树,递归构造右子树。
  2. 空出右子树,递归构造左子树。
  3. 左子树放叶子,递归构造右子树。
  4. 右子树放叶子,递归构造右子树。

可以发现,这样把集合 \(S_i\) 分成了4份。对于每份集合,必然存在无限种构造使该树不被其他集合的树包含。

对于4种情况,递归处理即可。

特别的,对于左右子树都是叶子的树 \(S\),意味着构造3,4都会被 \(S\) 包含,所以这种情况下只需要递归构造1,2即可。

理论上好像可以被卡到 \(O(n\sqrt n)\),但实际跑得飞快。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define N 3000010
using namespace std;
vector<int> ls[N],rs[N];
struct node{
    int t,u;
};
vector<node>id;
inline bool lev(node a){return !ls[a.t][a.u] && !rs[a.t][a.u];}
inline bool have_ls(node a){return ls[a.t][a.u];}
inline bool have_rs(node a){return rs[a.t][a.u];}
inline node lson(node a){return (node){a.t,ls[a.t][a.u]};}
inline node rson(node a){return (node){a.t,rs[a.t][a.u]};}
bool solve(vector<node> &s)
{
    if(s.empty()) return false;
    for(node i:s) if(lev(i)) return true;
    vector<node>s1,s2,s3,s4;
    bool can=false;
    for(node i:s)
    if(!have_ls(i)) s1.push_back(rson(i));
    else if(!have_rs(i)) s2.push_back(lson(i));
    else if(lev(lson(i)) && lev(rson(i))) can=true;
    else if(lev(lson(i))) s3.push_back(rson(i));
    else if(lev(rson(i))) s4.push_back(lson(i));
    s.clear();
    if(s1.empty() || s2.empty() || (!can && (s3.empty() || s4.empty()))) return false;
    return solve(s1) && solve(s2) && (can || (solve(s3) && solve(s4)));
}
int main()
{
    freopen("surreal.in","r",stdin);
    freopen("surreal.out","w",stdout);
    int t;
    scanf("%d",&t);
    while(t --> 0)
    {
        int n;
        scanf("%d",&n);
        id.resize(n);
        for(int i=0;i<n;i++)
        {
            int tn;
            scanf("%d",&tn);
            id[i]=(node){i,1};
            ls[i].resize(tn+1),rs[i].resize(tn+1);
            for(int j=1;j<=tn;j++) scanf("%d%d",&ls[i][j],&rs[i][j]);
        }
        puts(solve(id)?"Almost Complete":"No");
    }
    return 0;
}
posted @ 2020-08-20 20:54  Flying2018  阅读(511)  评论(0编辑  收藏  举报