P11962 [GESP202503 六级] 树上漫步
·
记录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代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
- 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。
核心解题思路
这道题表面上看是一个复杂的图论搜索问题,但如果我们深入分析树的结构特性,就会发现它本质上是一个二分图染色问题。
- 奇偶步规律:在一棵树中,任意两个节点之间有且仅有一条简单路径。这意味着,从节点 A 走到节点 B,所需的步数(即路径长度)的奇偶性是绝对固定的。
- 二分图特性:树是一种特殊的二分图,不存在奇数环。我们可以对整棵树进行“黑白染色”(即代码中的 0 和 1):相邻的节点颜色必须不同。
- 核心结论:
- 如果从节点 A 出发,走偶数步,那么最终停留的节点必定与节点 A 颜色相同。
- 如果走奇数步,最终停留的节点必定与节点 A 颜色不同。
- 降维打击:题目要求计算从每个节点出发,走偶数步能到达的节点数(允许重复经过节点)。根据上述结论,这等价于统计与当前节点颜色相同的节点总数。我们只需要进行一次 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]] |
直接查表输出 | 将复杂的“偶数步路径”问题转化为简单的“同色节点计数”问题 |
更多推荐



所有评论(0)