1. 程式人生 > 其它 >「NOI2020」超現實樹 題解

「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;
}