家庭问题

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

把每个关系看作无向边,用并查集合并家庭成员,最后按代表元统计家庭数与最大家庭规模。

OJ: roj

题目 ID: 1362

难度:入门

标签:并查集连通分量

创建: 2026-09-30 07:02

更新: 2026-10-05 12:00

题目描述

有 nn 个人(编号 1∼n1 \sim n)和 kk 个关系 (a,b)(a,b),表示 aa 与 bb 是同一家庭的成员,关系可传递。输入第一行 n,kn,k(1≤n≤1001 \le n \le 100),后接 kk 行每行两个整数;输出两个整数,即家庭个数与最大家庭人数。

样例输入

text
6 3
1 2
1 3
4 5

样例输出:3 3。

思路

把每个人看作点、每个关系看作无向边,同一连通块的人就是一家人,问题即求连通块个数与最大连通块大小。用并查集依次合并每个关系中的两人,再对每个人查代表元统计各家庭人数,人数非零的家庭数就是答案第一部分,其中的最大值是第二部分。

参考代码

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 07:05
# update_at: 2026-10-04 13:21

import sys
from collections import defaultdict


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    n = next(data, None)
    if n is None:                                        # 空输入:直接返回
        return
    k = next(data)
    edges = [(next(data), next(data)) for _ in range(k)]  # 每次顺序消费一对关系 (α, β)

    parent = list(range(n + 1))                          # 并查集:每个人的代表元

    def find(x: int) -> int:
        """带路径压缩查找 x 所在集合的代表元。"""
        while parent[x] != x:
            parent[x] = parent[parent[x]]                  # 路径减半
            x = parent[x]
        return x

    def union(x: int, y: int) -> None:
        """把 x、y 所在家庭合并。"""
        rx, ry = find(x), find(y)
        if rx != ry:
            parent[ry] = rx

    for x, y in edges:
        union(x, y)

    size = defaultdict(int)                              # 每个家庭代表元 → 人数
    for i in range(1, n + 1):
        size[find(i)] += 1

    families = len(size)                                 # 家庭总数
    largest = max(size.values()) if size else 0          # 最大家庭人数
    print(f"{families} {largest}")


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 12:00
 * update_at: 2026-10-05 12:00
 */
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 105;

typedef long long ll;

ll n, k;
ll parent[MAXN]; // parent[i] 表示第 i 个人所在家庭的代表元
ll cnt[MAXN];    // cnt[代表元] = 该家庭的人数

// 查找 x 所在家庭的代表元,路径减半压缩。
ll find_root(ll x) {
    while (parent[x] != x) {
        parent[x] = parent[parent[x]];
        x = parent[x];
    }
    return x;
}

// 把 x、y 所在的两个家庭合并。
void union_family(ll x, ll y) {
    ll rx = find_root(x);
    ll ry = find_root(y);
    if (rx != ry) {
        parent[ry] = rx;
    }
}

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

    cin >> n >> k;

    // 最初每个人自己就是一个家庭。
    for (ll i = 1; i <= n; i++) {
        parent[i] = i;
    }

    // 每读入一个关系就把它看作一条无向边,合并两个家庭。
    for (ll i = 1; i <= k; i++) {
        ll x, y;
        cin >> x >> y;
        union_family(x, y);
    }

    ll families = 0;  // 家庭个数
    ll largest = 0;   // 最大家庭人数
    for (ll i = 1; i <= n; i++) {
        cnt[find_root(i)]++;
    }
    for (ll i = 1; i <= n; i++) {
        if (cnt[i] > 0) {
            families++;
            if (largest < cnt[i]) {
                largest = cnt[i];
            }
        }
    }

    cout << families << " " << largest << "\n";
    return 0;
}