题解:洛谷 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

【核心思想】

  1. 问题分析:给定一棵 \(n\) 个节点的二叉树,每个节点为白色(\(0\))或黑色(\(1\))。进行 \(q\) 次操作,每次选择一个节点,将其子树内所有节点颜色反转。求 \(q\) 次操作后每个节点的颜色。这是一个树形结构 + 懒标记传递问题,核心在于利用"子树操作"的层次性,通过 DFS 自顶向下传递操作次数的奇偶性。

  2. 算法选择

    • 差分/计数标记:记录每个节点被直接操作的次数,通过 DFS 将父节点的操作次数累加到子节点
    • 奇偶性判定:颜色反转两次等于不变,因此只需关注操作次数的奇偶性
  3. 关键步骤

    • 读入数据:读取 \(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\)
      • 否则:颜色不变
    • 输出结果
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n + q)\),建树 \(O(n)\),处理操作 \(O(q)\),DFS \(O(n)\)
    • 空间复杂度:\(O(n)\),树结构和计数数组
  5. 树形懒标记传递的核心思想

    • 子树操作的层次性:对节点 \(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
posted @ 2026-07-17 15:26  团爸讲算法  阅读(5)  评论(0)    收藏  举报