[GESP202406 六级] 二叉树

news/2025/2/22 22:31:38

题目描述

小杨有⼀棵包含 n n n 个节点的二叉树,且根节点的编号为 1 1 1。这棵二叉树任意⼀个节点要么是白色,要么是黑色。之后小杨会对这棵二叉树进行 q q q 次操作,每次小杨会选择⼀个节点,将以这个节点为根的子树内所有节点的颜色反转,即黑色变成白色,白色变成黑色。

小杨想知道 q q q 次操作全部完成之后每个节点的颜色。

输入格式

第⼀行一个正整数 n n n,表示二叉树的节点数量。

第二行 ( n − 1 ) (n-1) (n1) 个正整数,第 i i i 1 ≤ i ≤ n − 1 1\le i\le n-1 1in1)个数表示编号为 ( i + 1 ) (i+1) (i+1) 的节点的父亲节点编号,数据保证是⼀棵二叉树。

第三行一个长度为 n n n 01 \texttt{01} 01 串,从左到右第 i i i 1 ≤ i ≤ n 1\le i\le n 1in)位如果为 0 \texttt{0} 0,表示编号为 i i i 的节点颜色为白色,否则为黑色。

第四行⼀个正整数 q q q,表示操作次数。

接下来 q q q 行每行⼀个正整数 a i a_i ai 1 ≤ a i ≤ n 1\le a_i\le n 1ain),表示第 i i i 次操作选择的节点编号。

输出格式

输出一行一个长度为 n n n 01 \texttt{01} 01 串,表示 q q q 次操作全部完成之后每个节点的颜色。从左到右第 i i i 1 ≤ i ≤ n 1\le i\le n 1in) 位如果为 0 \texttt{0} 0,表示编号为 i i i 的节点颜色为白色,否则为黑色。

输入输出样例 #1

输入 #1

6
3 1 1 3 4
100101
3
1
3
2

输出 #1

010000

说明/提示

样例解释

第一次操作后,节点颜色为: 011010 \texttt{011010} 011010

第二次操作后,节点颜色为: 000000 \texttt{000000} 000000

第三次操作后,节点颜色为: 010000 \texttt{010000} 010000

数据范围

子任务编号得分 n n n q q q特殊条件
1 1 1 20 20 20 ≤ 1 0 5 \le 10^5 105 ≤ 1 0 5 \le 10^5 105对于所有 i ≥ 2 i\ge 2 i2,节点 i i i 的父亲节点编号为 i − 1 i-1 i1
2 2 2 40 40 40 ≤ 1000 \le 1000 1000 ≤ 1000 \le 1000 1000
3 3 3 40 40 40 ≤ 1 0 5 \le 10^5 105 ≤ 1 0 5 \le 10^5 105

对于全部数据,保证有 n , q ≤ 1 0 5 n,q\le 10^5 n,q105

提交链接

二叉树

思路分析

暴力搜索(40分)

读取输入

cin >> n; // 读取二叉树的结点个数
for(int i = 2; i <= n; i++) {
    cin >> f[i];  // 读取每个节点的父节点编号
}
cin >> color;
color = ' ' + color;  // 让 color[1] 对应编号 1(为了方便索引)
cin >> q;
  • n n n 是二叉树的节点数,编号从 1 1 1 n n n
  • f [ i ] f[i] f[i] 记录 编号为 i i i 的节点的父节点(即 f [ i ] f[i] f[i] i i i 的直接父节点)。
  • c o l o r color color 是一个 01 01 01 字符串,表示初始颜色。
  • q q q 是操作次数。

记录操作次数

while(q--) {  
    cin >> x;  // 选择节点 x 作为操作的根
    num[x] = (num[x] + 1) % 2;  // 记录节点 x 被操作的奇偶性
}

n u m [ x ] num[x] num[x] 记录以 x x x 为根的子树被操作的次数的奇偶性:

  • 如果 n u m [ x ] num[x] num[x] 是偶数,说明 x x x 的子树 颜色未变。
  • 如果 n u m [ x ] num[x] num[x] 是奇数,说明 x x x 的子树 颜色被翻转了一次。

遍历所有节点并计算最终颜色

for(int i = 1; i <= n; i++) {
    sum = 0;
    dfs(i);  // 计算 i 节点受到多少次翻转影响
    if(sum & 1) {  // 如果翻转次数是奇数
        if(color[i] == '1') color[i] = '0';
        else color[i] = '1';
    }
}

核心思想:

  • 遍历所有 i i i,计算 i i i 这个节点从 它自身到根节点 经过的 翻转次数 s u m sum sum
  • 如果 s u m sum sum 是奇数,就翻转 i i i 这个节点的颜色。

计算 i i i 受到的翻转次数

void dfs(int x) {
    if(num[x]) sum++;  // 如果 x 这个节点被操作了,就增加翻转次数
    if(f[x] == 0) return;  // 递归终止:已经到达根节点
    dfs(f[x]);  // 继续递归到父节点
}

递归 d f s ( x ) dfs(x) dfs(x)

  • 如果 x x x 本身是翻转点( n u m [ x ] = = 1 num[x] == 1 num[x]==1), s u m + + sum++ sum++
  • 继续向 f [ x ] f[x] f[x](父节点)递归,直到到达根节点。
  • 这样 s u m sum sum 记录的是 从 x x x 到根节点的所有翻转次数。

输出最终颜色

for(int i = 1; i <= n; i++)
    cout << color[i];

直接输出所有节点最终的颜色。

存在的问题

d f s ( i ) dfs(i) dfs(i) 重复计算了很多次。

  • 每个 i i i 都要从自己走到根,而 有些 i i i 的路径是重复的,造成大量冗余计算。
  • 树高最多是 O ( n ) O(n) O(n),最坏情况下变成 O ( n 2 ) O(n²) O(n2)。如果树是链状结构,则 d f s ( i ) dfs(i) dfs(i) 的调用深度最坏是 n n n,遍历所有 n n n 个节点就会导致 O ( n 2 ) O(n²) O(n2) 复杂度。

参考代码:

#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 1e5 + 9;

int n , q , f[MAX_N] , x , num[MAX_N] , sum;
string color;

void dfs(int x)
{
    if(num[x])
        sum++;
    if(f[x] == 0)
        return;
    dfs(f[x]);
    
}

int main()
{
    cin >> n; //二叉树的结点个数
    for(int i = 2; i <= n; i++)
        cin >> f[i];  //f[i]:结点i的父亲结点的编号
    
    cin >> color;
    color = ' ' + color;  //color[i]:编号为i的结点的颜色,color[i]=1为黑色 color[i]=0为白色

    cin >> q;

    while(q--)  //q次询问
    {
        cin >> x;  //选择编号为x的结点
        num[x] = (num[x] + 1) % 2;  //记录根结点为x的子树操作的次数
    }

    for(int i = 1; i <= n; i++)
    {
        sum = 0;
        dfs(i);
        if(sum & 1)
        {
            if(color[i] == '1')
                color[i] = '0';
            else
                color[i] = '1';
        }
    }
    for(int i = 1; i <= n; i++)
        cout << color[i];
    return 0;
}

搜索优化(100分)

改进点:

  1. 改进翻转标记方式

    • 仍然使用 n u m [ x ] num[x] num[x] 记录操作次数,但不再逐个节点 D F S ( i ) DFS(i) DFS(i) 计算 s u m sum sum
    • 采用 子树 DFS 传播翻转状态,避免重复计算 s u m sum sum
  2. 高效的 DFS 处理方式

    • 构建子树关系:G[f[i]].push_back(i) 记录子节点,而不是用 f[x] 记录父节点。
    • 从根遍历一次 DFS 传递翻转状态:
      • c h e c k check check 变量标记当前节点是否应被翻转。
      • 遍历整个子树,每个节点仅访问一次,避免重复计算。
  3. 时间复杂度分析

    • 树的遍历是 O ( n ) O(n) O(n)
    • 建树 O ( n ) O(n) O(n)
    • 一次 DFS 处理整个树 O ( n ) O(n) O(n)
    • 总时间复杂度 O ( n ) O(n) O(n)

参考代码:

#include <bits/stdc++.h>
using namespace std;
const int MAX_N = 1e5 + 9;

int n , q , f[MAX_N] , x , num[MAX_N] , sum;
string color;
vector<int>G[MAX_N];

void dfs(int x)
{
    if(num[x])
        sum++;
    if(f[x] == 0)
        return;
    dfs(f[x]);
}

void dfs(int x , bool check)
{
    
    if(check && num[x])   //check为父结点带来的影响  num[x]为本身是否需要操作
        check = false;
    else if(!check && num[x] || check && !num[x])
        check = true;
    
    if(check)
    {
        if(color[x] == '1')
            color[x] = '0';
        else
            color[x] = '1';
    }

    for(auto it : G[x])
        dfs(it , check);
}

int main()
{
    cin >> n; //二叉树的结点个数
    for(int i = 2; i <= n; i++)
    {
        cin >> f[i];  //f[i]:结点i的父亲结点的编号
        G[f[i]].push_back(i);  //儿子存储法
    }
    
    cin >> color;
    color = ' ' + color;  //color[i]:编号为i的结点的颜色,color[i]=1为黑色 color[i]=0为白色

    cin >> q;

    while(q--)  //q次询问
    {
        cin >> x;  //选择编号为x的结点
        num[x] = (num[x] + 1) % 2;  //记录根结点为x的子树操作的次数
    }

    dfs(1 , false);

    for(int i = 1; i <= n; i++)
        cout << color[i];
    return 0;
}

http://www.niftyadmin.cn/n/5862809.html

相关文章

数据结构:双链表list

list 是 C 标准库中的双向链表容器。 list初始化示例&#xff1a; #include <list>int n 7;std::list<int> lst; // 初始化一个空的双向链表 lststd::list<int> lst(n); // 初始化一个大小为 n 的链表 lst&#xff0c;链表中的值默认都为 0std::list<i…

计算机网络:应用层 —— 文件传送协议 FTP

文章目录 FTP 是什么&#xff1f;FTP 的应用FTP 的基本工作原理主动模式被动模式 总结 FTP 是什么&#xff1f; 将某台计算机中的文件通过网络传送到可能相很远的另一台计算机中&#xff0c;是一项基本的网络应用&#xff0c;即文件传送。 文件传送协议FTP&#xff08;File T…

使用Docker Desktop部署GitLab

1. 环境准备 确保Windows 10/11系统支持虚拟化技术&#xff08;需在BIOS中开启Intel VT-x/AMD-V&#xff09;内存建议≥8GB&#xff0c;存储空间≥100GB 2. 安装Docker Desktop 访问Docker官网下载安装包安装时勾选"Use WSL 2 instead of Hyper-V"&#xff08;推荐…

【MCU输入捕获模式】

MCU输入捕获模式 目录 MCU输入捕获模式引言一、基本概念二、实现原理三、应用案例四、优势与局限五、配置与注意事项&#xff08;以STM32为例&#xff09; 引言 输入捕获模式 &#xff08;Input Capture Mode&#xff09;是一种用于捕获外部输入信号变化的微控制器&#xff08…

js中 ES6 新特性详解

ES6&#xff08;ECMAScript 2015&#xff09;是 JavaScript 的一次重大更新&#xff0c;引入了许多新的特性&#xff0c;使 JavaScript 代码更加简洁、可读和高效。以下是 ES6 的主要新特性及其原理 1. let 和 const 关键字 原理解析 1.1 作用域 var 关键字的作用域&#xf…

一篇文章理解常用的前端设计模式

前端设计模式 一.设计模式概览 设计模式是针对软件设计开发过程中反复出现的某类问题的通用解决方案。设计模式更多的是指导思想和方法论&#xff0c;而不是现成的代码&#xff0c;每种设计模式都有每种语言中的具体实现方式。学习设计模式更多是理解各个模式的内在思想和解决…

黑马点评自学03

分布式锁 分布式锁介绍 分布式锁&#xff1a;满足在分布式系统或者集群模式下多进程可见并且进程间获取锁的操作是互斥的锁。 在之前的测试中&#xff0c;当我们进入到集群或分布式的环境中时&#xff0c;一人一单业务在不同集群中可以被同时给用户id加锁&#xff0c;出现了…

娱乐使用,可以生成转账、图片、聊天等对话内容

软件介绍 今天要给大家介绍一款由吾爱大佬 lifeixue 开发的趣味软件。它的玩法超丰富&#xff0c;能够生成各式各样的角色&#xff0c;支持文字聊天、发红包、转账、发语音以及分享图片等多种互动形式&#xff0c;不过在分享前得着重提醒&#xff0c;此软件仅供娱乐&#xff0…