题解:洛谷 P10722 二叉树
【题目来源】
洛谷:P10722 [GESP202406 六级] 二叉树 - 洛谷
【题目描述】
小杨有一棵包含 \(n\) 个节点的二叉树,且根节点的编号为 \(1\)。这棵二叉树任意一个节点要么是白色,要么是黑色。之后小杨会对这棵二叉树进行 \(q\) 次操作,每次小杨会选择一个节点,将以这个节点为根的子树内所有节点的颜色反转,即黑色变成白色,白色变成黑色。
小杨想知道 \(q\) 次操作全部完成之后每个节点的颜色。
【输入】
第一行一个正整数 \(n\),表示二叉树的节点数量。
第二行 \((n-1)\) 个正整数,第 \(i\)(\(1\le i\le n-1\))个数表示编号为 \((i+1)\) 的节点的父亲节点编号,数据保证是一棵二叉树。
第三行一个长度为 \(n\) 的 \(\texttt{01}\) 串,从左到右第 \(i\)(\(1\le i\le n\))位如果为 \(\texttt{0}\),表示编号为 \(i\) 的节点颜色为白色,否则为黑色。
第四行一个正整数 \(q\),表示操作次数。
接下来 \(q\) 行每行一个正整数 \(a_i\)(\(1\le a_i\le n\)),表示第 \(i\) 次操作选择的节点编号。
【输出】
输出一行一个长度为 \(n\) 的 \(\texttt{01}\) 串,表示 \(q\) 次操作全部完成之后每个节点的颜色。从左到右第 \(i\)(\(1\le i\le n\))位如果为 \(\texttt{0}\),表示编号为 \(i\) 的节点颜色为白色,否则为黑色。
【输入样例】
6
3 1 1 3 4
100101
3
1
3
2
【输出样例】
010000
【核心思想】
-
问题分析:给定一棵 \(n\) 个节点的二叉树,每个节点为白色(\(0\))或黑色(\(1\))。进行 \(q\) 次操作,每次选择一个节点,将其子树内所有节点颜色反转。求 \(q\) 次操作后每个节点的颜色。这是一个树形结构 + 懒标记传递问题,核心在于利用"子树操作"的层次性,通过 DFS 自顶向下传递操作次数的奇偶性。
-
算法选择:
- 差分/计数标记:记录每个节点被直接操作的次数,通过 DFS 将父节点的操作次数累加到子节点
- 奇偶性判定:颜色反转两次等于不变,因此只需关注操作次数的奇偶性
-
关键步骤:
- 读入数据:读取 \(n\),构建二叉树(\(fa[i]\) 为父节点,\(lc[x]\) 和 \(rc[x]\) 为左右儿子)
- 读入初始颜色:字符串 \(s\)(\(0\) 为白,\(1\) 为黑)
- 读入操作:\(q\) 次操作,每次对节点 \(x\) 的 \(cnt[x]\) 加 \(1\)
- DFS 传递操作次数(\(dfs(x, f)\)):
- \(cnt[x] \leftarrow cnt[x] + cnt[f]\)(继承父节点的累计操作次数)
- 递归处理左子树和右子树
- 计算最终颜色(\(i\) 从 \(1\) 到 \(n\)):
- 若 \(cnt[i] \bmod 2 = 1\):颜色反转(\(0 \to 1, 1 \to 0\))
- 否则:颜色不变
- 输出结果
-
时间/空间复杂度:
- 时间复杂度:\(O(n + q)\),建树 \(O(n)\),处理操作 \(O(q)\),DFS \(O(n)\)
- 空间复杂度:\(O(n)\),树结构和计数数组
-
树形懒标记传递的核心思想:
- 子树操作的层次性:对节点 \(x\) 的操作会影响其所有后代节点。通过 DFS 先序遍历,将父节点的操作次数累加到子节点,实现"自顶向下"的影响传递
- 奇偶性简化:颜色反转两次等于不变,因此只需记录操作次数的奇偶性。\(cnt[x]\) 记录从根到 \(x\) 的路径上所有操作的总次数(包括 \(x\) 自身)
- 差分思想:每个节点的最终操作次数 = 自身被直接操作的次数 + 父节点的累计操作次数。这类似于差分数组在树上的推广
- 建树技巧:按输入顺序,节点 \(i\) 的父节点为 \(fa[i]\),优先作为左儿子,若左儿子已有则作为右儿子
- 适用于树上的区间/子树批量修改、自顶向下影响传递类问题
【算法标签】
普及 #DFS-树
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
// 树结构
int n, q; // n: 节点数, q: 操作次数
int fa[N]; // fa[i]: 节点i的父节点编号
int lc[N], rc[N]; // lc[i]: 节点i的左儿子编号, rc[i]: 节点i的右儿子编号
int cnt[N]; // cnt[i]: 节点i被直接操作的次数
string s; // s: 每个节点的字符('0'或'1')
// 深度优先搜索,将父节点的操作次数传递到子节点
// x: 当前节点
// f: 父节点的累计操作次数
void dfs(int x, int f)
{
// 当前节点的累计操作次数 = 直接操作次数 + 父节点的累计操作次数
cnt[x] += cnt[f];
// 递归处理左子树
if (lc[x])
{
dfs(lc[x], x);
}
// 递归处理右子树
if (rc[x])
{
dfs(rc[x], x);
}
}
int main()
{
// 输入节点数
cin >> n;
// 构建树结构
for (int i = 2; i <= n; i++) // 节点1是根节点
{
int x;
cin >> x; // 输入节点i的父节点
fa[i] = x; // 记录父节点关系
// 将当前节点i作为父节点x的子节点
// 先尝试作为左儿子,如果左儿子已有,则作为右儿子
if (!lc[x])
{
lc[x] = i; // 作为左儿子
}
if (lc[x] != i && !rc[x])
{
rc[x] = i; // 作为右儿子
}
}
// 输入每个节点的字符
cin >> s;
// 输入操作次数
cin >> q;
// 在字符串前加空格,使索引从1开始
s = ' ' + s;
// 处理每个操作
while (q--)
{
int x;
cin >> x; // 对节点x进行操作
cnt[x]++; // 记录节点x被操作的次数
}
// 从根节点开始DFS,传递操作次数
dfs(1, 0); // 根节点1的父节点累计操作为0
// 输出最终字符串
for (int i = 1; i <= n; i++)
{
// 如果节点i的总操作次数是奇数,字符取反
if (cnt[i] & 1) // cnt[i]是奇数
{
// 奇数次操作:'0'变'1','1'变'0'
cout << (s[i] == '0'); // 如果s[i]是'0',输出1;否则输出0
}
else // cnt[i]是偶数
{
// 偶数次操作:字符不变
cout << s[i];
}
}
cout << endl;
return 0;
}
【运行结果】
6
3 1 1 3 4
100101
3
1
3
2
010000
浙公网安备 33010602011771号