问题描述:
对一棵树进行着色,每个结点可着黑色或白色,相邻结点不能着相同黑色,但可着相同白色。请设计一种算法对树中尽量多的节点着黑色。
思路:
对于一棵根节点为
r
o
o
t
root
root 的子树(也有可能就是输入的树),只会发生两个情况:(1)
r
o
o
t
root
root 涂成白色,那么
r
o
o
t
root
root 的子节点可以涂成白色也可以涂成黑色;(2)
r
o
o
t
root
root 涂成黑色,那么root的子节点只能涂成白色。
基于这两种情况,我们可以维护一个动态规划表 d p [ N + 1 ] [ 2 ] dp[N + 1][2] dp[N+1][2],其中 d p [ r o o t ] [ 0 ] dp[root][0] dp[root][0] 代表了 r o o t root root 涂成黑色时以其为根节点的子树里面可包含的最大黑色节点数, d p [ r o o t ] [ 1 ] dp[root][1] dp[root][1] 代表了 r o o t root root 涂成白色时以其为根节点的子树里面可包含的最大黑色节点数。那么状态转移方程如下公式所示。
d p [ r o o t ] [ 0 ] = 1 + ∑ c h i l d d p [ c h i l d ] [ 1 ] d p [ r o o t ] [ 1 ] = 0 + ∑ c h i l d max ( d p [ c h i l d ] [ 1 ] , d p [ c h i l d ] [ 0 ] ) dp[root][0] = 1 + \sum_{child}{dp[child][1]} \\ dp[root][1] = 0 + \sum_{child}{\max(dp[child][1], dp[child][0])} dp[root][0]=1+child∑dp[child][1]dp[root][1]=0+child∑max(dp[child][1],dp[child][0])
很明显当访问到叶节点时(也就是最小子问题) d p [ r o o t ] [ 0 ] = 1 dp[root][0] = 1 dp[root][0]=1 、 d p [ r o o t ] [ 1 ] = 0 dp[root][1] = 0 dp[root][1]=0。
C++源代码:
//
// main.cpp
// ColoringTree
//
// Created by 胡昱 on 2021/12/31.
//
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
// 最大节点数
const int MAX_N = 50000;
// 动态规划数组
// dp[i][0]代表了第i个节点涂成黑色时,以其为根节点的子树的最大黑色节点数
// dp[i][1]代表了第i个节点涂成白色时,以其为根节点的子树的最大黑色节点数
int dp[MAX_N + 1][2];
// 记录每个节点是否有父节点
int hasP[MAX_N + 1];
// 邻接表(也就是每个节点的子节点)
vector<int>* chi;
// 递归形式的动态规划
void solve(int root) {
// 如果root涂成黑色,则目前这棵树的黑色节点数为1
dp[root][0] = 1;
// 如果root涂成白色,则目前这棵树的黑色节点数为0
dp[root][1] = 0;
// 对该节点的子节点进行动态规划
for(int i = 0; i < chi[root].size(); ++i) {
// 动态规划
solve(chi[root][i]);
// 如果root涂成黑色,那么子节点只能涂成白色
dp[root][0] += dp[chi[root][i]][1];
// 如果root涂成白色,那么子节点可以涂成白色也可以涂成黑色
dp[root][1] += max(dp[chi[root][i]][0], dp[chi[root][i]][1]);
}
}
int main(int argc, const char * argv[]) {
// 有T组测试数据
int T;
cin >> T;
while((T--) > 0) {
// 输入节点数量N
int N;
cin >> N;
// 初始化邻接表,注意节点编号是从1开始的
chi = new vector<int>[N + 1];
// 初始化记录是否有父节点的数组
memset(hasP, 0, sizeof(hasP));
// 输入边
for(int Ni = 0; Ni < N - 1; ++Ni) {
// u到v有一条边
int u, v;
cin >> u >> v;
chi[u].push_back(v);
// 记录存在父节点
hasP[v] = 1;
}
// 寻找根节点,从根节点向下动态规划
// root随便赋了个值防止编译器报错
int root = 0;
for(int Ni = 1; Ni <= N; ++Ni) {
if(hasP[Ni] == 0) {
root = Ni;
break;
}
}
// 初始化动态规划数组
memset(dp, 0, sizeof(dp));
// 开始动态规划
solve(root);
// 输出结果并释放资源
cout << max(dp[root][0], dp[root][1]) << endl;
delete [] chi;
}
return 0;
}