Loading

并查集

例题

一共有 \(n\) 个数,编号是 \(1 \sim n\),最开始每个数各自在一个集合中。

现在要进行 \(m\) 个操作,操作共有两种:

  1. M a b,将编号为 \(a\)\(b\) 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. Q a b,询问编号为 \(a\)\(b\) 的两个数是否在同一个集合中;

输入格式

第一行输入整数 \(n\)\(m\)

接下来 \(m\) 行,每行包含一个操作指令,指令为 M a bQ a b 中的一种。

输出格式

对于每个询问指令 Q a b,都要输出一个结果,如果 \(a\)\(b\) 在同一集合内,则输出 Yes,否则输出 No

每个结果占一行。

数据范围

\(1 \le n,m \le 10^5\)

输入样例:

4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4

输出样例:

No
Yes

什么是并查集

并查集常用于快速计算森林中有多少棵树。
并指合并操作,查指递归追查祖先,集指处理对象是集合。

如何实现并查集

初始化并查集

用一个一维数组 \(p\) 模拟并查集。开始时每个集合都是以自己为祖先
即:

for (int i = 1; i <= n; ++i)
    p[i] = i; // n为结点的个数(即数的个数)

合并操作

我们需要先实现一个函数:查找当前节点的祖先节点,在寻找祖先节点的过程中,使用类似记忆化搜索的方法同步更新父节点,这就是一种叫做路径压缩优化的技巧,可以有效减少追查深度。
代码:

int find(int x) { // 查找x的祖先节点(路径压缩)
    if (p[x] != x)
        p[x] = find(p[x]); // 祖先节点的父节点不是自己时更新父节点
    return p[x];
}

合并时,将两个集合的祖宗节点换掉就可以了

p[find(x)] = find(y);

查询操作

这个直接判断两个节点的祖宗节点是不是同一个节点就可以了,代码:

if (find(x) == find(y))
    puts("Yes");
else
    puts("No");

例题实现代码

#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int p[N];
int find(int x) {
    if (p[x] != x)
        p[x] = find(p[x]);
    return p[x];
}
int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i)
        p[i] = i;
    while (m--) {
        string op;
        int x, y;
        cin >> op >> x >> y;
        x = find(x), y = find(y);
        if (op == "M")
            p[x] = y;
        else {
            if (x == y)
                puts("Yes");
            else
                puts("No");
        }
    }
    return 0;
}
posted @ 2023-07-02 16:05  popcoount  阅读(9)  评论(0编辑  收藏  举报  来源