Jerry's Blog

Back

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

题目: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];
}
cpp

我们很容易就可以想到,对于两个节点 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;
}
cpp

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

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

复杂度分析#

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

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

标程#

并查集
https://blog.jerrylab.top/blog/ds/dsu
Author Jerry
Published at 2026年3月16日