【例3-1】找树根和孩子

GitHub跳转原题关系图返回列表

每条边只解析一次:parent[y]=x 定根,children[x] 记孩子;编号递增扫描 + 严格大于实现并列取小编号,O(n+m)。

OJ: roj

题目 ID: 1336

难度:入门

标签:入门树图论python

创建: 2026-09-30 05:56

更新: 2026-10-05 10:12

题目描述

nn 个编号 1∼n1\sim n 的结点和 mm 条有向边,每行 x y 表示 yy 是 xx 的孩子,保证构成一棵树(n≤100n\le 100,m≤200m\le 200,x,y≤1000x,y\le 1000)。求树根 root、孩子最多的结点 max(若并列取编号最小者),以及 max 的全部孩子(按编号升序输出)。样例:输入第一行 8 7,随后七行 4 1、4 2、1 3、1 5、2 6、2 7、2 8;输出三行为 4、2、6 7 8。

思路

每条边只解析一次:parent[y]=x 记下 yy 的父亲,children[x] 记下 xx 的孩子。树中只有根没有父亲,所以从 11 到 nn 递增扫描,第一个不在父亲表里的编号就是根;孩子表最长的结点就是 max(编号递增扫描配合严格大于比较,天然实现并列取小编号),它的孩子必须显式排序后再输出,因为输入不保证有序。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 05:56
# update_at: 2026-10-12 14:53

# 树的存储:父亲孩子表示法(每个结点记父亲 + 孩子列表)
# 一个结点的两个字段聚合成 Node 对象:父亲、孩子列表。
# 如何使用:
# 1. 读入每条边 (x, y):node[y].fa = x,node[x].child.append(y)
# 2. 根是唯一 fa == 0 的结点;孩子最多的结点取 child 最长者,并列取编号小的
# 3. 输出它的孩子前先排序——输入不保证有序

import sys
from collections.abc import Iterator

MAXN = 1005  # 题面结点编号 x,y <= 1000,按上界开


class Node:
    """编号 u 的结点:父亲 + 全部孩子(父亲孩子表示法)。"""

    def __init__(self) -> None:
        self.fa = 0               # 父亲编号,fa == 0 表示还没有人认领它(即根)
        self.child: list[int] = []  # 全部孩子编号,输入不保证有序


node: list[Node] = [Node() for _ in range(MAXN)]  # node[u]:编号 u 的结点


def pairs(nums: Iterator[int]) -> Iterator[tuple[int, int]]:
    """把 2m 个结点编号按 (父结点, 孩子) 两两成对。"""
    return zip(nums, nums)


def find_root(n: int) -> int:
    """根是唯一没当过孩子的点:读入时每个孩子都写了 fa,所以 fa == 0 的就是根。"""
    return next(i for i in range(1, n + 1) if node[i].fa == 0)


def find_busiest(n: int) -> int:
    """孩子最多的结点,并列取编号小者:max 只保留第一个最大值,
    而 range 递增,所以并列时自然取到编号小的结点。"""
    return max(range(1, n + 1), key=lambda v: len(node[v].child))


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    n, m = next(data), next(data)

    # 每行的 y 是 x 的孩子:x 的孩子表多一条,y 认下父亲 x
    for x, y in pairs(data):
        node[y].fa = x
        node[x].child.append(y)

    busiest = find_busiest(n)
    print(find_root(n))
    print(busiest)
    print(*sorted(node[busiest].child))  # 输出要求升序,输入不保证有序


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-05 10:12
 * update_at: 2026-10-12 14:56
 */
// main.cpp:用父亲孩子表示法存树,找根和孩子最多的结点,孩子升序输出。
//
// 每条边 (x, y) 只解析一次:y 认下父亲 x,x 多一个孩子 y。
// 结点的两个字段聚合成 struct:父亲、孩子列表。
// 根是唯一没当过孩子的点(fa == 0);孩子最多的结点按"编号递增扫描 + 严格大于"取,
// 并列时自然留下编号小的那个。

#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

typedef long long ll;

const int maxn = 1005; // 题面结点编号 x,y <= 1000,按上界开

struct Node {
    int fa;            // 父亲编号,fa == 0 表示还没有人认领它(即根)
    vector<int> child; // 全部孩子编号,输入不保证有序
};

Node node[maxn]; // node[u]:编号 u 的结点
ll n, m;         // 结点个数、边数

// 读入 m 条边:每条边一次写两处,y 认下父亲,x 多一个孩子
void read_input() {
    cin >> n >> m;
    for (ll i = 1; i <= m; i++) {
        int x, y;
        cin >> x >> y;
        node[y].fa = x;
        node[x].child.push_back(y);
    }
}

// 根是唯一没当过孩子的点:读入时每个孩子都写了 fa,所以 fa == 0 的就是根
ll find_root() {
    for (ll v = 1; v <= n; v++) {
        if (node[v].fa == 0) {
            return v;
        }
    }
    return 0; // 题面保证输入是一棵树,走到这里说明输入不合法
}

// 孩子最多的结点,并列取编号小者:
// 编号递增扫描配合严格大于,先扫到的小编号一定留得住
ll find_busiest() {
    ll best = 1;
    for (ll v = 1; v <= n; v++) {
        if (node[v].child.size() > node[best].child.size()) {
            best = v;
        }
    }
    return best;
}

void solve() {
    read_input();
    ll root = find_root();
    ll busiest = find_busiest();

    cout << root << "\n";
    cout << busiest << "\n";

    // 孩子的输出要求按编号从小到大,输入不保证有序,必须显式排序
    sort(node[busiest].child.begin(), node[busiest].child.end());
    for (size_t i = 0; i < node[busiest].child.size(); i++) {
        if (i > 0) {
            cout << " ";
        }
        cout << node[busiest].child[i];
    }
    cout << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    solve();
    return 0;
}