视频加载失败

并查集

1413 字
7 分钟
并查集

并查集是一种松散的数据结构,在求连通问题时很好用.

题目:P3367 【模板】并查集

题目描述#

如题,现在有一个并查集,你需要完成合并和查询操作.

输入格式#

第一行包含两个整数 N,MN,M ,表示共有 NN 个元素和 MM 个操作.

接下来 MM 行,每行包含三个整数 Zi,Xi,YiZ_i,X_i,Y_i

Zi=1Z_i=1 时,将 XiX_iYiY_i 所在的集合合并.

Zi=2Z_i=2 时,输出 XiX_iYiY_i 是否在同一集合内,是的输出

Y ;否则输出 N

输出格式#

对于每一个 Zi=2Z_i=2 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N

数据范围#

对于 100%100\% 的数据,1N2×1051\le N\le 2\times 10^51M1061\le M\le 10^61Xi,YiN1 \le X_i, Y_i \le NZi{1,2}Z_i \in \{ 1, 2 \}

思路#

首先,我们假定每一个集合都有一个节点作为代表,我们称那个节点是集合内其余节点的为领导节点

我们定义一个数组 ff ,其中,f[i]f[i] 表示的意义为:若 f[i]=if[i] = i,即节点 ii 是一个领导节点,则 f[i]f[i] 表示节点 ii 的领导节点.若 f[i]if[i] \neq i,则节点 ii 的领导节点就是节点 f[i]f[i] 的领导节点.

我们可以使用递归来求一个节点 aa 的领导节点,在求的同时,我们可以将求到的值保存到 f[a]f[a] 中,下次只需要使用 O(1)O(1) 的复杂度就可以求解了,这个行为叫做路径压缩

// 查找节点i的领导节点,并进行路径压缩
int find(int t)
{
if (f[t] != t)
f[t] = find(f[t]);
return f[t];
}

我们很容易就可以想到,对于两个节点 aabb,如果想要合并,则可以让 f[b]f[b] 设定为 aa 的领导节点.如果想要判断是否在同一集合中,只需要判断节点 aabb 的领导节点是否相同即可.

// 查询两个节点是否属于同一节点
bool query(int a, int b)
{
// 查询两个节点的领导节点是否一致
return find(a) == find(b);
}
// 合并两个节点
void merge(int a, int b)
{
int x = find(a), y = find(b);
if (x != y)
f[x] = y;
}

最后,千万不要忘了初始化,在最开始,所有节点的领导节点都是它自己.

// 初始化,一开始所有节点的领导节点都是它自己
void init()
{
for (int i = 1; i <= n; i++)
f[i] = i;
}

复杂度分析#

首先,find 函数的复杂度看似好像是最坏 O(n)O(n) 的,但是由于有了路径压缩,每一次的 find 都会大大减少下一次 find 相同节点的时间,最终无限趋近于 O(1)O(1),但常常带有较大的常数.

因此,查询和合并的复杂度也是 O(1)O(1),综合一下,mm 次操作的总复杂度约为 O(mlogn)O(m\log n)

标程#

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 6;
int n, m;
// 定义一个并查集
class DSU
{
private:
// f[i]表示第i个节点的领导节点
int f[N];
// 查找节点i的领导节点,并进行路径压缩
int find(int t)
{
if (f[t] != t)
f[t] = find(f[t]);
return f[t];
}
public:
DSU()
{
this->init();
}
// 初始化,一开始所有节点的领导节点都是它自己
void init()
{
for (int i = 1; i <= n; i++)
f[i] = i;
}
// 查询两个节点是否属于同一节点
bool query(int a, int b)
{
// 查询两个节点的领导节点是否一致
return find(a) == find(b);
}
// 合并两个节点
void merge(int a, int b)
{
int x = find(a), y = find(b);
if (x != y)
f[x] = y;
}
} dsu;
int main()
{
cin >> n >> m;
dsu.init();
for (int i = 1; i <= m; i++)
{
int z, x, y;
cin >> z >> x >> y;
if (z == 1)
{
dsu.merge(x, y);
}
else
{
cout << (dsu.query(x, y) ? "Y" : "N") << endl;
}
}
return 0;
}

带权并查集#

题目:P2024 食物链

题目描述#

动物王国中有三类动物 A,B,CA,B,C,这三类动物的食物链构成了有趣的环形.AABBBBCCCCAA

现有 NN 个动物,以 1N1 \sim N 编号.每个动物都是 A,B,CA,B,C 中的一种,但是我们并不知道它到底是哪一种.

有人用两种说法对这 NN 个动物所构成的食物链关系进行描述:

  • 第一种说法是 1 X Y,表示 XXYY 是同类.
  • 第二种说法是 2 X Y,表示 XXYY

此人对 NN 个动物,用上述两种说法,一句接一句地说出 KK 句话,这 KK 句话有的是真的,有的是假的.当一句话满足下列三条之一时,这句话就是假话,否则就是真话.

  • 当前的话与前面的某些真的话冲突,就是假话;
  • 当前的话中 XXYYNN 大,就是假话;
  • 当前的话表示 XXXX,就是假话.

你的任务是根据给定的 NNKK 句话,输出假话的总数.

输入格式#

第一行两个整数,N,KN,K,表示有 NN 个动物,KK 句话.

第二行开始每行一句话.格式见题目描述与样例.

输出格式#

一行,一个整数,表示假话的总数.

说明/提示#

对于全部数据,1N5×1041\le N\le 5 \times 10^41K1051\le K \le 10^5

X,Y<232\lvert X \rvert, \lvert Y \rvert < 2^{32}

考虑对于每一个加入并查集的元素,尝试对于每一个元素都维护一个权值 ss,权值为 0 的吃权值为 1 的,1 吃 2,2 吃 0.权值大于 2 则模 3 处理.

定义 d[i]d[i] 表示节点 ii 与节点 f[i]f[i] 的权值之差.

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

并查集
https://blog.jerrylab.top/posts/ds/dsu/
作者
Jerry
发布于
2026-03-16
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Jerry
Hello, I'm Jerry.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
80
分类
3
标签
35
总字数
89,198
运行时长
0
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0