题目传送门

题目描述

给定一棵由 \(n\) 个节点构成的树,可以使用两种颜色(假定为红和蓝)进行染色,并且每个节点可以选择染红色、蓝色或者不染色。设红色的节点有 \(r\) 个,蓝色的节点有 \(b\) 个,要求染色后的节点同时满足以下三个条件:

\(r + b\) 最大化;
\(r,b \neq 0\)
任意两个相邻的节点不能被染成相同的颜色

询问满足要求的染色方案数及 \(r\)\(b\) 的所有可能值。

思路

一道 DP 好题!
不难发现两种颜色总和最大一定是 \(n-1\),及最多只能有一个节点不被染色。又注意到条件三:任意两个相邻的节点颜色不相同,可以得出结论一定有且仅有一个节点不被染色(这个结论并不难想,看不明白的建议画图推导一下)。
于是我们想到可以枚举不染色的节点,进行 DP 来推导总共有 \(k\) 个节点染成红色的方案可行性。
如何计算?放两张图。

屏幕截图 2025-12-17 155059

假设这是初始的树,现在我们枚举到节点 \(5\) 是没有颜色的节点。

屏幕截图 2025-12-17 155123

可以看出删除一个节点后原树会被分成几个小连通块,所以我们只需要根据原树的子树大小计算出每个小块的大小,类似于 01 背包处理即可。
于是现在这个思路很完整了。

实现

在思路完整之后,代码的实现其实并不难。只需要注意几个小小的细节:dfs 求取子树大小 siz[i],输出答案时需要枚举 $i \in \left [ 1 \sim n-2 \right ] $。

Code

#include<bits/stdc++.h>
using namespace std;
vector<int> G[5005];
int siz[5005],dp[5005],c[5005],ans[5005],n;
void dfs(int u,int father){
	siz[u]=1;
	for(auto v:G[u]){
		if(v==father) continue;
		dfs(v,u);
		siz[u]+=siz[v];
	}
} //计算子树大小。
void find_ans(int u,int father){
	int tot=0;
	for(auto v:G[u]){
		if(v==father) continue;
		c[++tot]=siz[v]; //将不染色节点的儿子子树大小加入类似于重量的 c 数组。
	}
	c[++tot]=n-siz[u]; //由于删除一个节点后,不包含在该节点子树中的连通块至多只有一个,所以只需用原树大小减去该节点子树大小。
	memset(dp,0,sizeof(dp));
	dp[0]=1; //初始化。
	for(int i=1;i<=tot;i++)
		for(int j=n;j>=c[i];j--)
			if(dp[j-c[i]]==1) dp[j]=1; //如果 j-c[i] 的方案可行,那么 j 也可行。
	for(int i=1;i<=n;i++) ans[i]=max(ans[i],dp[i]);
	for(auto v:G[u]){
		if(v==father) continue;
		find_ans(v,u); //继续 dfs。
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<n;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		G[u].push_back(v),G[v].push_back(u);
	}
	dfs(1,0);
	find_ans(1,0);
	int cnt=0;
	for(int i=1;i<n-1;i++) //i < n-1 是因为至少有一个蓝点和一个不染色节点。
		if(ans[i]==1) cnt++; //求方案数。
	printf("%d\n",cnt);
	for(int i=1;i<n-1;i++)
		if(ans[i]==1) printf("%d %d\n",i,n-i-1);
	return 0;
} 

\(\sim\) ❀完结撒花❀ \(\sim\)