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

【核心思想】

  1. 问题分析:给定一棵有根二叉树,需要统计所有子树中满二叉树的数量。满二叉树的定义是:所有叶子深度相同,且每个非叶子结点都有两个儿子。这是一个树形动态规划 + 深度优先搜索(DFS)问题,核心在于自底向上判断每个子树是否满足满二叉树的两个条件。

  2. 算法选择

    • 深度优先搜索(DFS):从根结点开始递归遍历,先处理子结点再处理父结点,实现自底向上的信息传递
    • 状态标记:每个结点维护两个状态——\(flag[x]\)(是否为满二叉树)和 \(dep[x]\)(子树高度/叶子深度)
  3. 关键步骤

    • 叶子结点判定\(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\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),每个结点仅访问一次,每次处理为 \(O(1)\)
    • 空间复杂度:\(O(n)\),递归栈深度最坏为 \(O(n)\),数组存储为 \(O(n)\)
  5. 树形 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
posted @ 2026-07-07 17:48  团爸讲算法  阅读(8)  评论(0)    收藏  举报