记录126

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;//定义常量N,表示树的最大节点数量(根据题目数据范围设定)
vector<int>g[N];//定义邻接表g,用来存储树的边连接关系
int color[N];//定义数组color,用来记录每个节点的染色情况(0代表一种颜色,1代表另一种)
int cnt[2];//定义数组cnt,用来统计两种颜色各自的节点总数量
int n;//定义全局变量n,表示树的结点总数
void dfs(int u,int c){//定义深度优先搜索函数,u是当前节点,c是当前节点的颜色
	color[u]=c;//将当前节点u染成颜色c
	cnt[c]++;//该颜色的节点计数器加一
	for(int v:g[u]){//遍历当前节点u的所有相邻节点v
		if(color[v]==-1){//如果相邻节点v还没有被染色(初始值为-1)
			dfs(v,c^1);//递归访问节点v,并将其染成与u相反的颜色(c^1实现0变1,1变0)
		}
	}
}
int main(){//主函数入口
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n;//读入树的结点总数n
	for(int i=1;i<n;i++){//循环读入n-1条边的信息
		int u,v;//定义临时变量u和v,代表一条边连接的两个结点
		cin>>u>>v;//读入这条边连接的两个结点编号
		g[u].push_back(v);//在邻接表中添加u到v的连接
		g[v].push_back(u);//在邻接表中添加v到u的连接(因为是无向树)
	}
	memset(color,-1,sizeof(color));//将color数组全部初始化为-1,表示所有节点均未染色
	dfs(1,0);//从1号节点开始进行DFS染色,将其初始颜色设为0
	for(int i=1;i<=n;i++){//遍历树上的每一个结点(从1到n)
		cout<<cnt[color[i]]<<" ";//输出当前结点i所属颜色的总节点数,并加上空格分隔
	}
	return 0;//程序正常结束
}

题目传送门https://www.luogu.com.cn/problem/P11962


前言

我是一名专注信奥赛(CSP-J/S、NOIP)的教练。

  • 如果你觉得这篇题解对你有帮助,欢迎点击关注我的CSDN账号,我会持续更新高质量算法解析。
  • 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
  • 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。

 核心解题思路

这道题表面上看是一个复杂的图论搜索问题,但如果我们深入分析树的结构特性,就会发现它本质上是一个二分图染色问题。

  1. 奇偶步规律:在一棵树中,任意两个节点之间有且仅有一条简单路径。这意味着,从节点 A 走到节点 B,所需的步数(即路径长度)的奇偶性是绝对固定的。
  2. 二分图特性:树是一种特殊的二分图,不存在奇数环。我们可以对整棵树进行“黑白染色”(即代码中的 0 和 1):相邻的节点颜色必须不同。
  3. 核心结论
    • 如果从节点 A 出发,走偶数步,那么最终停留的节点必定与节点 A 颜色相同
    • 如果走奇数步,最终停留的节点必定与节点 A 颜色不同
  4. 降维打击:题目要求计算从每个节点出发,走偶数步能到达的节点数(允许重复经过节点)。根据上述结论,这等价于统计与当前节点颜色相同的节点总数。我们只需要进行一次 DFS 遍历,统计出颜色 0 和颜色 1 的节点各有多少个,最后直接输出对应颜色的总数即可。时间复杂度从暴力搜索的 O(N2) 完美优化到了 O(N) 。

代码分块详细解释

1. 头文件、常量与全局变量定义

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10; // 定义常量 N,表示树的最大节点数量(根据题目数据范围 2*10^5 设定)
vector<int>g[N];    // 定义邻接表 g,用来存储树的边连接关系
int color[N];       // 定义数组 color,用来记录每个节点的染色情况(0代表一种颜色,1代表另一种)
int cnt[2];         // 定义数组 cnt,用来统计两种颜色各自的节点总数量
int n;              // 定义全局变量 n,表示树的结点总数
  • 作用:完成基础数据结构的定义。使用全局数组和 vector 邻接表,避免在 main 函数中开辟过大的局部数组导致栈溢出。

2. 核心逻辑:DFS 染色与统计

void dfs(int u,int c){ // 定义深度优先搜索函数,u是当前节点,c是当前节点的目标颜色
    color[u]=c;        // 将当前节点 u 染成颜色 c
    cnt[c]++;          // 该颜色的节点计数器加一
    for(int v:g[u]){   // 遍历当前节点 u 的所有相邻节点 v
        if(color[v]==-1){ // 如果相邻节点 v 还没有被染色(初始值为 -1)
            dfs(v,c^1);   // 递归访问节点 v,并将其染成与 u 相反的颜色(c^1 实现 0 变 1,1 变 0)
        }
    }
}
  • 作用:这是整个算法的核心。通过 DFS 遍历整棵树,利用异或运算 c^1 巧妙地实现相邻节点颜色交替。在遍历的过程中,顺便完成了两种颜色节点数量的统计。

3. 输入与初始化

int main(){ // 主函数入口
    ios::sync_with_stdio(false);
    cin.tie(0); // 关闭输入输出同步,提升读取速度
    cin>>n; // 读入树的结点总数 n
    for(int i=1;i<n;i++){ // 循环读入 n-1 条边的信息
        int u,v; // 定义临时变量 u 和 v,代表一条边连接的两个结点
        cin>>u>>v; // 读入这条边连接的两个结点编号
        g[u].push_back(v); // 在邻接表中添加 u 到 v 的连接
        g[v].push_back(u); // 在邻接表中添加 v 到 u 的连接(因为是无向树)
    }
    memset(color,-1,sizeof(color)); // 将 color 数组全部初始化为 -1,表示所有节点均未染色
    dfs(1,0); // 从 1 号节点开始进行 DFS 染色,将其初始颜色设为 0
  • 作用:读入树的结构,构建无向图。将颜色数组初始化为 -1 作为未访问的标记,随后从根节点 1 开始执行染色操作。

4. 输出结果

    for(int i=1;i<=n;i++){ // 遍历树上的每一个结点(从 1 到 n)
        cout<<cnt[color[i]]<<" "; // 输出当前结点 i 所属颜色的总节点数,并加上空格分隔
    }
    return 0; // 程序正常结束
}
  • 作用:根据前面统计好的 cnt 数组,直接以 O(1)O(1) 的时间复杂度查出每个节点对应的偶数步可达节点数,并格式化输出。

核心逻辑总结表

代码模块 核心变量/操作 精炼作用 解决的痛点
图构建 g[u].push_back(v) 构建无向树邻接表 存储节点间的连通关系,为遍历做准备
状态初始化 memset(color,-1,...) 标记所有节点未访问 防止 DFS 过程中产生死循环或重复计算
DFS 染色 dfs(v,c^1) 相邻节点颜色交替 利用二分图特性,将节点划分为奇偶两层
全局统计 cnt[c]++ 累加各颜色节点总数 避免对每个节点单独进行搜索,将复杂度降至 O(N)
结果映射 cnt[color[i]] 直接查表输出 将复杂的“偶数步路径”问题转化为简单的“同色节点计数”问题

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐