zoukankan      html  css  js  c++  java
  • 「NOI2020」超现实树 题解

    最近做了不少数据结构“好题”(笑)产生了奇怪的审美疲劳,于是决定做思维题沐浴自闭神的智慧之光。

    定义一个操作「单步逆替换」为「单步替换」的逆操作(即,将树 (T) 的一棵子树替换成一个结点,这个结点显然是叶结点)。假设现在有一棵随机的树,我们显然可以选择一条根到某一个叶结点的链,然后对链上的每一个点进行操作:如果这个点有两棵子树,将不包含链的那棵子树进行「单步逆替换」;否则不管。例如:

    发现我们生成的树有一个显然的特点是,左右儿子子树大小的最小值不超过 (1)

    定义这种树为「自闭树」。显然所有的自闭树能够生成所有的树,并且非自闭树不能够生成自闭树。不难猜测我们可以将非自闭树移出这个二叉树的体系中。整个问题也就变得只和自闭树有关了。问题变成:给定一个自闭树的集合,问这些自闭树不能够生长成的自闭树的数量是否有限。

    发现,如果所有深度为 (d) 的自闭树能够被生成,这意味着所有深度大于等于 (d) 的自闭树能被生成,因此不能够被生成的自闭树有限。

    一般性质挖掘到这里。考虑将几乎完备这一性质转移到结点上,使得这个森林的几乎完备可以被递归定义。又考虑一些特殊性质:

    • 如果这个结点对应了之前森林中某一棵树的叶结点,可以确定其几乎完备;
    • 否则,对于(所有的树合并后形成的)一个树的结点,如果下列四种情况全部存在即完备:
      • 只有左子树;
      • 只有右子树;
      • 右子树大小为 (1),有左子树;
      • 左子树大小为 (1),有右子树。

    将四种情况看成一棵四叉树。如果当前结点是满的,那么其是几乎完备的。

    于是直接递归处理。合并的时候记忆当前结点是否对应之前树的集合的某一棵树的叶结点,然后按四种情况向下递归。

    #include<bits/stdc++.h>
    using namespace std;
    char buf[1<<21],*p1=buf,*p2=buf;
    #define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<18,stdin),p1==p2)?EOF:*p1++)
    int read()
    {
    	int x=0;
    	char c=getchar();
    	while(c<'0' || c>'9')	c=getchar();
    	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
    	return x;
    }
    int lc[2000005],rc[2000005],siz[2000005],ch[2000005][4],cnt,rt,tek[2000005];
    bool isSizeTree(int now)
    {
    	if(!now)	return true;
    	if(!lc[now] && !rc[now])	return (siz[now]=1);
    	bool flag=(!lc[now] || isSizeTree(lc[now])) && (!rc[now] || isSizeTree(rc[now]));
    	siz[now]=siz[lc[now]]+siz[rc[now]]+1;
    	return flag && bool(min(siz[lc[now]],siz[rc[now]])<=1);
    }
    void Merge(int &now,int tnw)
    {
    	if(!now)	now=++cnt;
    	if(!siz[lc[tnw]] && !siz[rc[tnw]])
    	{
    		tek[now]=1;
    		return ;
    	}
    	if(!siz[rc[tnw]] && siz[lc[tnw]]>=1)	Merge(ch[now][0],lc[tnw]);
    	if(!siz[lc[tnw]] && siz[rc[tnw]]>=1)	Merge(ch[now][1],rc[tnw]);
    	if(siz[rc[tnw]]==1 && siz[lc[tnw]]>=1)	Merge(ch[now][2],lc[tnw]);
    	if(siz[lc[tnw]]==1 && siz[rc[tnw]]>=1)	Merge(ch[now][3],rc[tnw]);
    }
    bool check(int now)
    {
    	if(tek[now])	return true;
    	if(!ch[now][0] || !ch[now][1] || !ch[now][2] || !ch[now][3])	return false;
    	return check(ch[now][0]) && check(ch[now][1]) && check(ch[now][2]) && check(ch[now][3]);
    }
    void Solve()
    {
    	for(int i=1;i<=cnt;++i)	ch[i][0]=ch[i][1]=ch[i][2]=ch[i][3]=tek[i]=0;
    	rt=cnt=0;
    	int Size=read();
    	bool flag=false;
    	while(Size-->0)
    	{
    		int n=read();
    		for(int i=1;i<=n;++i)	lc[i]=read(),rc[i]=read();
    		if(n==1)	flag=true;
    		if(isSizeTree(1))	Merge(rt,1);
    	}
    	puts((flag || check(rt))?"Almost Complete":"No");
    }
    int main(){
    	int T=read();
    	while(T-->0)	Solve();
    	return 0;
    }
    
  • 相关阅读:
    常用python机器学习库总结
    链接器link.exe 编译器cl.exe 资源编译器rc.exe
    LRESULT与wParam和lParam的问题
    CreateDialog和DialogBox
    如何通俗易懂地解释卷积?
    深度学习在graph上的使用
    一文弄懂神经网络中的反向传播法——BackPropagation
    WM_COMMAND消息
    win32编程中消息循环和WndProc()窗口过程函数
    使用UEditor 的时候,ajax注意使用同步的方法
  • 原文地址:https://www.cnblogs.com/amagaisite/p/15222285.html
Copyright © 2011-2022 走看看