题解:洛谷 P17013 满二叉树
【题目来源】
洛谷:P17013 [GESP202606 六级] 满二叉树 - 洛谷
【题目描述】
给定一棵包含 \(n\) 个结点的有根二叉树,结点依次以 \(1, 2, \dots, n\) 编号,根结点编号为 \(1\)。
对于结点 \(i\),其左儿子的编号记为 \(l_i\),右儿子编号记为 \(r_i\)。特别地,如果左儿子不存在则 \(l_i = 0\),如果右儿子不存在则 \(r_i = 0\)。
树中每个结点都对应一棵以其为根的子树。请你求出给定有根树的所有 \(n\) 棵子树中,有多少棵子树是满二叉树。
满二叉树是指所有叶子深度均相同,且除叶子外均有两个儿子的二叉树,例如以下三棵二叉树均是满二叉树:
() () ()
/ \ / \
() () () ()
/ \ / \
() ()() ()
在上面这棵有 \(3\) 个节点的二叉树中,有 \(3\) 个子树是满二叉树(包括整个树本身,及所有的单个叶子节点);
又例如,
(1)
/ \
(2) (3)
/ \
(4) (5)
在上面这棵有 \(5\) 个节点的二叉树中,有 \(4\) 个子树是满二叉树(包括节点 \(3\) 的子树,以及所有单个叶子节点)。
【输入】
第一行,一个正整数 \(n\),表示有根二叉树结点数量。
接下来 \(n\) 行,每行两个非负整数 \(l_i, r_i\),表示结点 \(i\) 的左儿子编号和右儿子编号,整数之间以空格分隔。
【输出】
输出一行,一个整数,表示所有子树中满二叉树的数量。
【输入样例】
4
2 3
4 0
0 0
0 0
【输出样例】
2
【核心思想】
-
问题分析:给定一棵有根二叉树,需要统计所有子树中满二叉树的数量。满二叉树的定义是:所有叶子深度相同,且每个非叶子结点都有两个儿子。这是一个树形动态规划 + 深度优先搜索(DFS)问题,核心在于自底向上判断每个子树是否满足满二叉树的两个条件。
-
算法选择:
- 深度优先搜索(DFS):从根结点开始递归遍历,先处理子结点再处理父结点,实现自底向上的信息传递
- 状态标记:每个结点维护两个状态——\(flag[x]\)(是否为满二叉树)和 \(dep[x]\)(子树高度/叶子深度)
-
关键步骤:
- 叶子结点判定(\(l_x = 0\) 且 \(r_x = 0\)):
- \(flag[x] \leftarrow true\),单个叶子结点是满二叉树
- \(dep[x] \leftarrow 1\),叶子高度为 \(1\)
- \(ans \leftarrow ans + 1\)
- 递归处理子树:若左儿子存在则 \(dfs(l_x)\),若右儿子存在则 \(dfs(r_x)\)
- 满二叉树判定(以 \(x\) 为根的子树):
- 条件:\(flag[l_x] = true\) 且 \(flag[r_x] = true\) 且 \(dep[l_x] = dep[r_x]\)
- 满足时:\(flag[x] \leftarrow true\),\(dep[x] \leftarrow dep[l_x] + 1\),\(ans \leftarrow ans + 1\)
- 非满二叉树处理:
- \(flag[x] \leftarrow false\),\(dep[x] \leftarrow \max(dep[l_x], dep[r_x]) + 1\)
- 输出结果:\(ans\)
- 叶子结点判定(\(l_x = 0\) 且 \(r_x = 0\)):
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\),每个结点仅访问一次,每次处理为 \(O(1)\)
- 空间复杂度:\(O(n)\),递归栈深度最坏为 \(O(n)\),数组存储为 \(O(n)\)
-
树形 DP 与 DFS 的核心思想:
- 自底向上信息聚合:通过 DFS 后序遍历,先获取子结点的 \(flag\) 和 \(dep\) 信息,再判断父结点是否满足满二叉树条件,确保子问题已解决
- 满二叉树的递归定义:一棵子树是满二叉树,当且仅当左右子树都是满二叉树且高度相同。这一定义天然适合递归判断
- 高度统一性:\(dep[x]\) 表示以 \(x\) 为根的子树中叶子到 \(x\) 的最大距离,满二叉树要求左右子树高度严格相等,保证所有叶子深度一致
- 计数策略:在判定每个结点为满二叉树时立即累加 \(ans\),利用 DFS 遍历所有子树,自然统计所有满足条件的子树
- 适用于树形结构的递归性质判定、自底向上状态传递类问题
【算法标签】
普及- #DFS-树
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100005; // 常量:最大结点数
int n; // n: 二叉树结点数量
int ans; // ans: 满二叉树子树的数量
int l[N], r[N]; // l[i]: 结点 i 的左儿子编号; r[i]: 结点 i 的右儿子编号
int dep[N]; // dep[i]: 以结点 i 为根的子树高度(叶子深度)
bool flag[N]; // flag[i]: 以结点 i 为根的子树是否为满二叉树
void dfs(int x) // 深度优先搜索,计算以 x 为根的子树信息
{
if (l[x] == 0 && r[x] == 0) // 叶子结点:左右儿子都不存在
{
flag[x] = true; // 单个叶子结点是满二叉树
dep[x] = 1; // 叶子结点的高度为 1
ans++; // 满二叉树计数加一
return;
}
if (l[x] != 0) // 如果左儿子存在
dfs(l[x]); // 递归计算左子树
if (r[x] != 0) // 如果右儿子存在
dfs(r[x]); // 递归计算右子树
// 判断以 x 为根的子树是否为满二叉树:
// 条件:左右子树都是满二叉树,且左右子树高度相同
if (flag[l[x]] == 1 && flag[r[x]] == 1 && dep[l[x]] == dep[r[x]])
{
flag[x] = 1; // 标记为满二叉树
dep[x] = dep[l[x]] + 1; // 当前子树高度 = 子树高度 + 1
ans++; // 满二叉树计数加一
}
else // 不是满二叉树
{
flag[x] = 0; // 标记为非满二叉树
dep[x] = max(dep[l[x]], dep[r[x]]) + 1; // 高度取左右子树的最大值
}
}
signed main()
{
cin >> n; // 读入结点数量
for (int i = 1; i <= n; i++) // 读入每个结点的左右儿子编号
{
cin >> l[i] >> r[i];
}
dfs(1); // 从根结点开始 DFS
cout << ans << endl; // 输出满二叉树子树的总数
return 0;
}
【运行结果】
4
2 3
4 0
0 0
0 0
2
浙公网安备 33010602011771号