「ZJOI2006」三色二叉树 题解
「ZJOI2006」三色二叉树 题解
题意
给出一颗二叉树,要求对这颗二叉树染色,可以染成红、绿、蓝三种颜色,要求节点和这个结点的子节点颜色不同,问最多和最少可以染多少个点是绿色。
题解
本题有两个难点,如何存二叉树和求答案。
对于给出的字符串,可以发现这个字符串是以结点 + 子树 的形式呈现的,所以可以用一个递归来实现存二叉树的操作。
比如这里, 我使用一个 \(dfs\),在递归的同时,返回这个子树的大小,对于有右儿子的结点,只有返回了左儿子的大小,才可以确定右儿子的位置。
这个 \(dfs\) 用孩子表示法记录, 即记录每个结点的左儿子和右儿子。
明显, 对于求答案的步骤,用 \(\texttt{DP}\) 实现。
因为我们只关心染成绿色的节点数量,所以可以设一个二维DP,用 \(f_{i, j}\) 表示,\(i\) 表示以 \(i\) 为根节点的子树的染色点数, \(j\) 只有 2 维, 表示是否是绿色, 下文中 1 表示不是绿色, 0 表示是绿色。
这里的 DP 是换根 DP, 因为设的状态与根节点有关。
然后可以分类讨论一下, 因为求最大值和求最小值过程相同, 所以这里只写求最大值的过程。
设现在处理到了 \(f_u\), \(v\) 是 \(u\) 的子节点。
当 \(u\) 只有一个儿子时:
由于要保证颜色不同, 于是:
- \(f_{u, 1} = f_{v, 0} + 1\), 表示这个点的子节点 \(v\) 可以被染成绿色,从子节点的状态转移过来。
- \(f_{u, 0} = \max(f_{v,0} + f_{v, 1})\), 这个结点已经是绿色了,所以答案是它子节点中更大的,因为颜色有三种,无论怎么染颜色,都可以满足子结点和这个结点的子节点颜色不同的条件而取到。
当 \(u\) 有两个儿子时:
同上:
- \(f_{u, 1} = f_{v_1, 0} + f_{v_2, 0} + 1\)子节点能被染到的点个数之和加这个点。
- \(f_{u, 0} = \max(f_{v_1, 1} + f_{v_2, 0}, f_{v_1, 0} + f_{v_2, 1})\), 这里因为子节点的颜色已经确定, 要满足条件,子节点互相颜色也不能相同,所以答案就是上面的柿子。
Code
#include <bits/stdc++.h>
using i64 = long long;
constexpr int N = 1000000;
char s[N + 10];
int fa[N + 1], l[N + 1], r[N + 1];
int siz[N + 1];
// 0 表示是绿色 1 表示不是绿色
int f_min[N + 1][2], f_max[N + 1][2];
int main() {
std::cin >> (s + 1);
int now = 0;
std::function<int(int)> dfs_tree = [&](int u) {
if (s[u] == '0') return 1;
else if (s[u] == '1') {
l[u] = u + 1;
return dfs_tree(u + 1) + 1;
} else {
l[u] = u + 1;
int t = dfs_tree(u + 1);
r[u] = u + 1 + t;
int tt = dfs_tree(u + 1 + t);
return t + tt + 1;
}
};
int n = strlen(s + 1);
for (int i = 1; i <= n; i++) {
siz[i] += l[i] + r[i];
if (!siz[i]) f_max[i][1] = f_min[i][1] = 1;
}
std::function<void(int)> dfs_dp = [&](int u) {
if (!u) return;
int ls = l[u], rs = r[u];
dfs_dp(ls), dfs_dp(rs);
if (ls && rs) {
f_max[u][0] = std::max(f_max[ls][1] + f_max[rs][0], f_max[ls][0] + f_max[rs][1]);
f_min[u][0] = std::min(f_min[ls][1] + f_min[rs][0], f_min[ls][0] + f_min[rs][1]);
f_max[u][1] = f_max[ls][0] + f_max[rs][0] + 1;
f_min[u][1] = f_min[ls][0] + f_min[rs][0] + 1;
}
if (ls && !rs) {
f_max[u][0] = std::max(f_max[ls][1], f_max[ls][0]);
f_min[u][0] = std::min(f_min[ls][1], f_min[ls][0]);
f_max[u][1] = f_max[ls][0] + 1;
f_min[u][1] = f_min[ls][0] + 1;
}
};
dfs_tree(1);
dfs_dp(1);
std::function<void()> test = [&]() {
for (int i = 1; i <= n; i++) {
std::cout << i << " " << f_max[i][0] << " " << f_max[i][1] << std::endl;
}
for (int i = 1; i <= n; i++) {
std::cout << i << " " << f_min[i][0] << " " << f_min[i][1] << std::endl;
}
};
// test();
int ans1 = std::max(f_max[1][1], f_max[1][0]), ans2 = std::min(f_min[1][1], f_min[1][0]);
std::cout << ans1 << " " << ans2 << "\n";
return 0;
}