解决的问题

并查集解决了两个问题:

  1. 迅速将两个集合合并成一个集合。
  2. 迅速查询两个元素是否在同一集合中。

原理

将一个集合看成一棵树,树根的编号就是整个集合的编号。每一个节点都可以找到自己的父节点,p[x]表示x的父节点。

问题

  1. 如何判断树根:p[x] == x
  2. 如何求x的集合编号:while ( p[x] != x ) x = p[x];
  3. 如何合并两个集合:只需要修改其中一个集合根元素的编号就可以了。假设p[x]p[y]xy集合的编号,只需要p[x] = y或者p[y] = x即可。

路径压缩优化

如何一个集合树比较高,查找路径会比较长,最差可退化成 O(N)O(N) 。所以在查询的时候,可以将路径中每一个节点直接指向根节点。

这样优化后,查询效率几乎逼近 O(1)O(1)

问题是如何解决的

  1. 修改两个集合中任意一个根节点的值即可。
  2. 对于节点xy,只需判断xy节点的根节点是否相等即可。

例子

原题链接

题目描述

一共有 nn 个数,编号是 1n1∼n,最开始每个数各自在一个集合中。

现在要进行 mm 个操作,操作共有两种:

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

输入格式

第一行输入整数 nnmm

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

输出格式

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

每个结果占一行。

数据范围

1n,m1051 \le n,m \le 10^5

输入样例:

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

输出样例:

Yes
No
Yes

代码

#include <iostream>
#include <string>

using namespace std;

const int N = 100010;

int p[N], n, m;

int find(int x)
{
    // 涵路径压缩
    return p[x] == x ? p[x] : p[x] = find(p[x]);
}

int main() 
{
    cin >> n >> m;
    for ( int i = 1; i <= n; i ++ )
        p[i] = i;
    
    string op;
    int a, b;
    while ( m -- )
    {
        cin >> op >> a >> b;
        if ( op == "M" ) p[find(a)] = p[find(b)];
        else cout << (p[find(a)] == p[find(b)] ? "Yes" : "No") << endl;
    }
    return 0;
}