P17013 [GESP202606 六级] 满二叉树

题目描述

给定一棵包含 nnn 个结点的有根二叉树,结点依次以 1,2,…,n1, 2, \dots, n1,2,,n 编号,根结点编号为 111

对于结点 iii,其左儿子的编号记为 lil_ili,右儿子编号记为 rir_iri。特别地,如果左儿子不存在则 li=0l_i = 0li=0,如果右儿子不存在则 ri=0r_i = 0ri=0

树中每个结点都对应一棵以其为根的子树。请你求出给定有根树的所有 nnn 棵子树中,有多少棵子树是满二叉树。

满二叉树是指所有叶子深度均相同,且除叶子外均有两个儿子的二叉树,例如以下三棵二叉树均是满二叉树:

()   ()        ()
    /  \      /  \
   ()  ()   ()    ()
           /  \  /  \
          ()  ()()  ()

在上面这棵有 333 个节点的二叉树中,有 333 个子树是满二叉树(包括整个树本身,及所有的单个叶子节点);

又例如,

   (1)
   / \
 (2) (3)
     / \
   (4) (5)

在上面这棵有 555 个节点的二叉树中,有 444 个子树是满二叉树(包括节点 333 的子树,以及所有单个叶子节点)。

输入格式

第一行,一个正整数 nnn,表示有根二叉树结点数量。

接下来 nnn 行,每行两个非负整数 li,ril_i, r_ili,ri,表示结点 iii 的左儿子编号和右儿子编号,整数之间以空格分隔。

输出格式

输出一行,一个整数,表示所有子树中满二叉树的数量。

输入输出样例 #1

输入 #1

4
2 3
4 0
0 0
0 0

输出 #1

2

输入输出样例 #2

输入 #2

3
2 3
0 0
0 0

输出 #2

3

说明/提示

数据范围

对于 40%40\%40% 的测试点,保证 1≤n≤5001 \le n \le 5001n500

对于所有测试点,保证 1≤n≤1051 \le n \le 10^51n105

题解

本题要求统计一棵有根二叉树中,所有子树(即以每个结点为根的子树)中有多少棵是满二叉树。
满二叉树的定义是:所有叶子深度相同,且除叶子外每个结点都有两个儿子。

我采用后序遍历(DFS)自底向上计算每个结点的信息:

  • d[x]:以结点 x 为根的子树的高度(深度)。若为满二叉树,则所有叶子深度相同,高度定义为从根到叶子的边数加 1(即叶子深度为 1)。
  • b[x]:布尔值(用 0/1 表示),标记以 x 为根的子树是否是满二叉树。

递归过程:

  1. 叶子结点l[x]==0 && r[x]==0):显然是一棵满二叉树,d[x]=1b[x]=1,答案加 1。
  2. 非叶子结点:先递归处理左右子树,然后判断:
    • 若左、右子树都存在且都是满二叉树(b[l[x]]==1 && b[r[x]]==1),并且左右子树高度相等(d[l[x]] == d[r[x]]),则当前子树也是满二叉树,高度为 d[l[x]]+1,标记 b[x]=1,答案加 1。
    • 否则当前子树不是满二叉树,b[x]=0,不增加答案。

注意:如果某个结点只有一个儿子,则不可能成为满二叉树,因为满二叉树要求除叶子外都有两个儿子,所以这种情况直接不满足条件(代码中逻辑隐含了 b[0] 为 0 且 d[0] 为 0,但左/右儿子为 0 时不满足 b[l[x]] && b[r[x]],故不会错误计数)。

最终输出答案 ans

时间复杂度:每个结点访问一次,O(n)O(n)O(n)n≤105n \le 10^5n105 完全可行。
空间复杂度O(n)O(n)O(n)


带注释的源代码

#include <bits/stdc++.h>
using namespace std;

int n;
int l[100005];   // l[i]:结点 i 的左儿子编号,0 表示不存在
int r[100005];   // r[i]:结点 i 的右儿子编号,0 表示不存在
int d[100005];   // d[i]:以 i 为根的子树高度(若为满二叉树则有效)
int b[100005];   // b[i]:1 表示以 i 为根的子树是满二叉树,0 表示不是
int ans = 0;     // 统计满二叉树子树的个数

// 后序遍历结点 x
void dfs(int x) {
    // 情况1:x 是叶子结点
    if (l[x] == 0 && r[x] == 0) {
        d[x] = 1;       // 叶子高度为 1
        b[x] = 1;       // 叶子是满二叉树
        ans++;          // 计数
        return;
    }

    // 先递归处理左右子树(若存在)
    if (l[x] > 0) dfs(l[x]);
    if (r[x] > 0) dfs(r[x]);

    // 情况2:x 有左右儿子,且左右子树都是满二叉树,且高度相等
    // 注意:如果某个儿子不存在,则 b[0] 默认为 0,条件不成立
    if (b[l[x]] && b[r[x]] && d[l[x]] == d[r[x]]) {
        d[x] = d[l[x]] + 1;   // 当前子树高度 = 子树高度 + 1
        b[x] = 1;             // 当前子树是满二叉树
        ans++;                // 计数
    }
    // 否则当前子树不是满二叉树,b[x] 保持为 0(全局默认),不需要额外操作
    return;
}

int main() {
    cin >> n;
    // 读入每个结点的左右儿子
    for (int i = 1; i <= n; i++) {
        cin >> l[i] >> r[i];
    }

    // 从根结点 1 开始后序遍历
    dfs(1);

    // 输出满二叉树子树的个数
    cout << ans;
    return 0;
}

更多推荐