「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;
}
posted @ 2022-02-11 16:53  落花月朦胧  阅读(81)  评论(1编辑  收藏  举报