Junie's Blog

并查集(DSU)基础

全文共 927预计阅读 4 分钟

并查集

并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。常常在使用中以森林来表示。

操作:

  1. 将两个集合合并。
  2. 询问两个元素是否在一个集合中。

基本原理:

每个集合用一棵树来表示。树根的编号就是整个集合的编号。每个节点的值储存它的父节点编号,p[x]p[x] 表示 xx 的父节点。

问题:

Question1Question1: 如何判断是不是根节点?

AnswerAnswer: 判断 p[x]p[x] 是否等于 xx ,即 xx 的父节点是否是它本身。

Question2Question2: 如何求 xx 的集合编号(根节点)?

AnswerAnswer: 朴素可以通过 while (p[x] != x) x = p[x] 求得。即当 xx 不是根节点时,不断向上求直到是根节点。

Question3Question3: 如何合并两个集合?

AnswerAnswer: 若 aaxx 的根节点编号,bbyy 的根节点编号,令 p[a]=bp[a] = b ,即让 xx 的集合的根节点的父节点变成 yy 集合的根节点。

重要优化:路径压缩

在上面的 Question2Question2 中我们可以发现,在查找一个节点的根节点时,需要不断向上找,耗时长。我们可以通过路径压缩,让每个节点直接指向根节点。

这里需要利用递归实现。

int find(int x)
{
    return x == p[x] ? p[x] : p[x] = find(p[x]);
}

find\mathrm{find} 函数可以实现寻找根节点的同时进行路径压缩。

例题:带货援助

题目描述

张三学姐找人帮忙带货。有 nn 个带货达人,已知让每个带货达人帮忙带货的价格是 cic_i,如果花钱雇了一个带货达人,因为他们之间经常互动一起带货,所以花钱雇的这个带货达人的朋友(朋友也是 nn 个带货达人中的一员)也会帮忙免费带货,已知有 nn 个人, mm 对朋友。

请你可以帮她算一下最少可以只花多少钱就可以让所有带货达人都帮忙带货。

输入格式

第一行包含两个整数 nnmm (1n105,0m105)(1 ≤ n ≤ 10^5, 0 ≤ m ≤ 10^5),分别表示张三学姐认识的带货达人的数量,以及存在多少对的朋友关系。

第二行包含 nn 个整数 cic_i (0ci109)(0 ≤ c_i ≤ 10^9) 表示雇佣第 ii 个带货达人需要的人民币,以便开始带货。

接下来的 mm 行,每行包含两个数 (xi,yi)(x_i, y_i),表示带货达人 xix_iyiy_i 是朋友关系 (1xi,yin,xiyi)(1 ≤ x_i, y_i ≤ n, x_i ≠ y_i)。数据保证,每对朋友最多被列出一次。

输出格式

输出一个数,表示张三学姐雇佣所有带货达人的最少金钱。

输入输出样例

输入输出
5 2 2 5 3 5 9 1 4 4 510
10 0 1 2 3 4 5 6 7 8 9 1055
10 5 1 6 2 7 3 8 4 9 5 101 23 45 67 89 1015
分析:

按照题意,我们需要将是朋友的带货达人放在一个集合里,并且只需要花费这个集合里所需的最小的价值就能让这个集合所有的带货达人帮忙带货,对于没有朋友关系的带货达人,则只能花费相应的金钱让他帮忙带货。

所以我们用并查集,先将有朋友关系的带货达人的编号放进同一个集合,同时,我们可以把根节点的值变为其中最小的那个价值。最后,我们遍历所有的集合,求根节点的价值之和。(单独的带货达人,根节点就是他自己的编号)

#include <iostream>
using namespace std;
const int N = 1e5+10;
int a[N], p[N];
int find(int x)
{
    return x == p[x] ? p[x] : p[x] = find(p[x]);
}
int main()
{
    int n, m;
    cin >> n >> m;
    for (int i=1;i<=n;i++) p[i] = i; //初始化根节点的值
    for (int i=1;i<=n;i++) cin >>a[i];
    while (m--)
    {
        int x, y;
        cin >> x>> y;
        if (find(x) != find(y))
        {
            if (a[find(x)] > a[find(y)]) p[find(x)] = find(y); //集合合并操作,让需要金钱更多的带货达人的根节点变为需要金钱更少的带货达人
            else p[find(y)] = find(x);
        }
    }
    long long ans = 0;
    for (int i=1;i<=n;i++)
    {
        if (p[i] == i) ans += a[i];
    }
    cout << ans << endl;
}

原题链接:

评论