资料分发 1

OJ: luogu

题目 ID: P2097

标签: 并查集dfs

日期: 2026-01-01 21:03

这道题 P2097 资料分发 1 是非常经典的 图论基础题

题目解析

  • 题意转化
    • 电脑之间有双向连线,意味着这是一个 无向图
    • 只要一台电脑得到数据,整个连通网络里的电脑都能得到。
    • 问题问“至少输入几台电脑”,其实就是问这个图里有几个 连通块 (Connected Components)
    • 我们只需要在每一个连通块里挑出一台电脑输入数据,整个块就都点亮了。
  • 算法选择
    • 求连通块个数,最常用的两种方法是 DFS/BFS 遍历并查集 (Union-Find / DSU)
    • 对于这道题,并查集 代码最短、最适合作为模板使用,而且处理 10510^5 级别的数据非常快。

代码

cpp
#include <iostream>
#include <vector>
#include <numeric> // for iota

using namespace std;

const int N = 100005; // 根据题目 n <= 10^5

// --- 并查集模板开始 ---
struct DSU {
    int fa[N];

    // 初始化:每个人都是自己的父亲
    void init(int n) {
        for (int i = 1; i <= n; i++) {
            fa[i] = i;
        }
    }

    // 查找 + 路径压缩 (Path Compression)
    // 这一步是让并查集变快的核心
    int find(int x) {
        if (x == fa[x]) return x;
        return fa[x] = find(fa[x]); // 递归找根,并把沿途节点的父亲直接设为根
    }

    // 合并 x 和 y 所在的集合
    void merge(int x, int y) {
        int fx = find(x);
        int fy = find(y);
        if (fx != fy) {
            fa[fx] = fy; // 把 x 的老大指向 y 的老大
        }
    }
    
    // 查询是否在同一个集合
    bool same(int x, int y) {
        return find(x) == find(y);
    }
};
// --- 并查集模板结束 ---

DSU dsu;
int n, m;

int main() {
    // 优化输入输出效率
    ios::sync_with_stdio(false);
    cin.tie(0);

    if (!(cin >> n >> m)) return 0; // 防止读取错误

    // 1. 初始化并查集
    dsu.init(n);

    // 2. 读入边并合并
    for (int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        dsu.merge(u, v);
    }

    // 3. 统计连通块数量
    // 方法:遍历所有点,如果一个点的父亲是它自己,说明它是这个集合的根
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        if (dsu.fa[i] == i) {
            ans++;
        }
    }

    cout << ans << endl;

    return 0;
}