#18009. Not a Work of Idol 2 题解

题目链接:#18009. Not a Work of Idol 2

很厉害!

考虑把值按照 $ 1, n, 2, n-1, \cdots $ 填进去,可以把原问题转换成一个删点顺序的问题:称偶数轮的数为大数,奇数轮的数为小数,显然在大数轮选择删掉的点必须是孤立点(因为与其相连的点都应该是在它删掉之前的小数(删掉的点),并且每次删掉的是孤立点可以满足不会出现两个大数相连的情况)。

显然对于最后两个点的删去方式总是对称的,因此答案肯定是 2 的倍数,因此在除了最后两个点的时候全程不应该出现超过 1 个孤立点(超过一个就会存在 2 种删除方式,答案就是 4 的倍数了),并且全程不应该同时出现大于等于 4 个孤立点(在模 4 意义下正确性显然)。那么我们可以发现合法的孤立点序列只有以下三种:

\[[1, 1, 1, \cdots , 1] \]

\[[1, 1, 1, \cdots , 1, 2, 0] \]

\[[1, 1, 1, \cdots , 1, 3, 1] \]

发现三种分别相当于最后剩下一个有 1 个,2 个,3 个叶子结点的菊花,考虑剩下节点删除的合法性。设每两轮删去的节点分别为 $ (u,v) $,显然有删除时 $ u $ 是 $ v $ 的父亲,并且 $ u $ 在当前时刻只有一个子节点,那么删除的方式会变得相当固定:对于子树大小为奇数的节点,显然要其所有儿子的子树大小都为偶数,且能全部在自己子树内匹配,此时这个节点会被留下给其父亲匹配。对于子树大小为偶数的节点,显然要其儿子子树的大小只有一个为奇数,其他儿子子树大小均为偶数且都是合法的(偶数能在子树内解决匹配,奇数只会留下一个点),该节点会和子树大小为奇数的那个节点匹配。考虑将每对匹配缩成一个点,对于原树中扣掉菊花的部分会留下若干个树,每个树内的儿子节点删去的时间都是在其父亲之前(否则删除后留下的不是单个孤立点,正确性显然)。

对于缩点后的一个森林设点集为 $ T $,设总共有 $ k $ 个点,则有不同的删除顺序数量为:

\[\frac{k!}{\prod_{u \in T} \frac{sz_u}{2}} \]

那么显然这个是好计算的,因为只需要维护这个值模 2 的值,进行一个 dp 根的换即可。

代码:

#include<bits/stdc++.h>
#define time(null) chrono::steady_clock::now().time_since_epoch().count()
#define int long long
#define uint unsigned long long
#define debug() cout<<"come here\n"
#define INF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define pb push_back
#define Code return
#define by 0
#define MCYYDS ;
using namespace std;
int qpow(int a,int b,int p=INF){int ret=1;while(b){if(b&1)ret=(ret*a)%p;a=(a*a)%p;b>>=1;}return ret;}
inline int read(){int ret=0,f=1;char ch=getchar();while(ch<'0'||ch>'9')f=(ch=='-'?-1:f),ch=getchar();while(ch>='0'&&ch<='9')ret=(ret<<3)+(ret<<1)+(ch^48),ch=getchar();return ret*f;}
inline void write(int x){if(x<0){putchar('-');write(-x);return ;}if(x>9)write(x/10);putchar((char)(x%10+48));}
inline void writech(int x,char ch){write(x);putchar(ch);}
int sz[1000005],son[1000005],ans,cnt,tot,n;
vector<int> e[1000005];
int check(int siz,int ctt)
{
	if(siz&1)return (ctt!=0);
	return (ctt!=1);
}
int getctz(int siz)
{
	return (siz%2==0&&siz!=0?__builtin_ctz(siz/2):0);
}
void dfs(int u,int fa)
{
	sz[u]=1;
	for(auto v:e[u])
	{
		if(v==fa)continue;
		dfs(v,u);
		sz[u]+=sz[v];
		if(sz[v]&1)son[u]++;
	}
}
void dfs2(int u,int fa)
{
	int msz=son[u]+1;
	if(cnt==0&&msz<=4&&(n-msz)/2-__builtin_popcount((n-msz)/2)==tot)ans++;
	for(auto v:e[u])
	{
		if(v==fa)continue;
		sz[u]-=sz[v];
		son[u]-=(sz[v]&1);
		cnt-=check(sz[v],son[v]);
		tot-=getctz(sz[v]);
		sz[v]+=sz[u];
		son[v]+=(sz[u]&1);
		cnt+=check(sz[u],son[u]);
		tot+=getctz(sz[u]);
		dfs2(v,u);
		tot-=getctz(sz[u]);
		cnt-=check(sz[u],son[u]);
		son[v]-=(sz[u]&1);
		sz[v]-=sz[u];
		tot+=getctz(sz[v]);
		cnt+=check(sz[v],son[v]);
		son[u]+=(sz[v]&1);
		sz[u]+=sz[v];
	}
}
void solve()
{
	n=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		e[u].pb(v);
		e[v].pb(u);
	}
	if(n==1)
	{
		puts("1");
		return ; 
	}
	if(n==2)
	{
		puts("2");
		e[1].clear();
		e[2].clear();
		return ;
	}
	dfs(1,0);
	ans=cnt=tot=0;
	for(int i=2;i<=n;i++)
	{
		cnt+=check(sz[i],son[i]);
		tot+=getctz(sz[i]);
	}
	dfs2(1,0);
	writech((ans%2)*2,'\n');
	for(int i=1;i<=n;i++)
	{
		e[i].clear();
		sz[i]=son[i]=0;
	}
}
signed main()
{
//	ios::sync_with_stdio(0);
//	cin.tie(0);
//	cout.tie(0);
	int T=read();
	while(T--)
	{
		solve();
	}
	Code by MCYYDS
}

posted @ 2026-07-22 10:05  MCYYDS  阅读(7)  评论(0)    收藏  举报