题解:洛谷 P2978 Tea Time

【题目来源】

洛谷:P2978 [USACO10JAN] Tea Time S - 洛谷

【题目描述】

\(N (1 \leq N \leq 1000)\) 头奶牛,编号为 \(1 \dots N\),在参加一个喝茶时间活动。在喝茶时间活动开始之前,已经有 \(M (1 \leq M \leq 2,000)\) 对奶牛彼此认识(是朋友)。第i对彼此认识的奶牛通过两个不相同的整数 \(A_i\)\(B_i\) 给定(\(1 \leq A_i \leq N, 1 \leq B_i \leq N\))。输入数据保证一对奶牛不会出现多次。在喝茶时间活动中,如果奶牛 \(i\) 和奶牛 \(j\) 有一个相同的朋友奶牛 \(k\),那么他们会在某次的喝茶活动中去认识对方(成为朋友),从而扩大他们的社交圈。请判断,在喝茶活动举办很久以后(直到没有新的奶牛彼此认识),\(Q (1 \leq Q \leq 100)\) 对奶牛是否已经彼此认识。询问j包含一对不同的奶牛编号 \(X_j\)\(Y_j\)\(1 \leq X_j \leq N, 1 \leq Y_j \leq N\))。

例如,假设共有 \(5\) 头奶牛,我们知道 \(2\) 号认识 \(5\) 号,\(2\) 号认识 \(3\) 号,而且 \(4\) 号认识 \(5\) 号;如下图(a)。

   2---3           2---3            2---3
    \              |\  |            |\ /|
1    \     -->  1  | \ |    -->  1  | X |
      \            |  \|            |/ \|
   4---5           4---5            4---5
    (a)             (b)              (c)

在第一次喝茶活动中,\(2\) 号认识 \(4\) 号,\(3\) 号认识 \(5\) 号,如上图(b)所示。接下来的喝茶活动中,\(3\) 号认识了 \(4\) 号,如上图(c)所示。

【输入】

\(1\) 行:三个空格隔开的整数:\(N\)\(M\)\(Q\)

\(2 \dots M+1\) 行:第 \(i+1\) 行包含两个空格隔开的整数 \(A_i\)\(B_i\)

\(M+2 \dots M+Q+1\) 行:第 \(j+M+1\) 行包含两个空格隔开的整数 \(X_j\)\(Y_j\),表示询问 \(j\)

【输出】

\(1 \dots Q\) 行:如果第 \(j\) 个询问的两头奶牛认识,第 \(j\) 行输出 Y。如果不认识,第 \(j\) 行输出 N

【输入样例】

5 3 3
2 5
2 3
4 5
2 3
3 5
1 5

【输出样例】

Y
Y
N

【核心思想】

  1. 问题分析:给定 \(N\) 头奶牛和 \(M\) 对初始朋友关系,朋友关系具有传递性(若 \(i\)\(k\) 是朋友,\(k\)\(j\) 是朋友,则 \(i\)\(j\) 也会成为朋友)。求 \(Q\) 对奶牛最终是否认识。这是一个并查集/传递闭包问题,关键在于用并查集维护朋友关系的连通分量,同一连通分量内的奶牛互相认识。

  2. 算法选择

    • 并查集(Union-Find):用路径压缩和按秩合并维护连通分量,支持 \(O(\alpha(N))\) 的查询和合并
    • 传递性抽象:将"通过共同朋友介绍而认识"的传递关系抽象为图的连通性,同一连通分量即互相认识
  3. 关键步骤

    • 初始化\(fa[i] = i\)\(i\)\(1\)\(N\)
    • 合并朋友关系\(M\)\((A_i, B_i)\)):
      • \(unionn(A_i, B_i)\):将 \(A_i\)\(B_i\) 所在集合合并
    • 查询处理\(Q\)\((X_j, Y_j)\)):
      • \(find(X_j) = find(Y_j)\):输出 Y(同一连通分量,认识)
      • 否则:输出 N(不同连通分量,不认识)
  4. 时间/空间复杂度

    • 时间复杂度:\(O((M + Q) \cdot \alpha(N))\),并查集操作近似常数时间
    • 空间复杂度:\(O(N)\),父节点数组
  5. 并查集的核心思想

    • 集合代表元:每个连通分量有一个代表元(祖先节点),\(find(x)\) 返回 \(x\) 所在集合的代表元
    • 路径压缩:查询时将访问过的节点直接指向祖先,加速后续查询
    • 传递闭包的动态维护:无需显式计算所有传递关系,通过合并操作隐式维护连通性
    • 适用于"动态连通性 + 等价类查询"类问题,核心是维护集合的合并与查询

【解题思路】

【算法标签】

普及- #并查集

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, m, q, fa[1005];
void init(int n)  // 并查集初始化
{
    for (int i=1; i<=n; i++) {  // 每个节点的父节点都是自己
        fa[i] = i;
    }
}
int find(int x)  // 查找某个节点的祖先节点
{
    if (fa[x]==x) {  // 如果节点的祖先节点就是自己
        return x;  // 那就到达递归出口,返回
    } else {  // 否则
        fa[x] = find(fa[x]);  // 路径压缩,查过一次后,fa[x]就直接指向了祖先节点
        return fa[x];  // 返回这个节点的父节点
    }

}
void unionn(int i, int j)  // 合并操作
{
    int i_fa = find(i);  // 找到i节点的祖先节点
    int j_fa = find(j);  // 找到j节点的祖先节点
    fa[i_fa] = j_fa;  // 将i节点的祖先指向j的祖先
}
int main()
{
    cin >> n >> m >> q;  // 输入n、m和q
    init(n);  // 初始化所有节点的父节点
    for (int i=1; i<=m; i++) {  // 输入m对节点对,将其进行合并
        int x, y;
        cin >> x >> y;
        unionn(x, y);
    }
    for (int i=1; i<=q; i++) {  // 依次遍历q次询问
        int x, y;
        cin >> x >> y;
        if (find(x)==find(y)) cout << "Y" << endl;  // 如果x和y的祖先节点相同,则说明他们认识
        else cout << "N" << endl;
    }
    return 0;
}

【运行结果】

5 3 3
2 5
2 3
4 5
2 3
Y
3 5
Y
1 5
N
posted @ 2026-08-28 15:56  团爸讲算法  阅读(3)  评论(0)    收藏  举报