并查集算法的多条件维护

37 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第36天,点击查看活动详情

并查集干了什么?

在前一篇文章中,我们知道并查集是对于存在相同关系的两个数可以存储在一个并查集(家族集)的一种数据结构,那么并查集起到了什么样的作用呢?

在前一篇文章中,并查集只是简简单单的、基础的维护了两个数之间的关系(是否在一个集合中),而对于这篇文章将要讲如果维护多条件:

acwing——837. 连通块中点的数量

题目描述

给定一个包含 n 个点(编号为 1∼n)的无向图,初始时图中没有边。

现在要进行 m 个操作,操作共有三种:

  1. C a b,在点 a 和点 b 之间连一条边,a 和 b 可能相等;
  2. Q1 a b,询问点 a 和点 b 是否在同一个连通块中,a 和 b 可能相等;
  3. Q2 a,询问点 a 所在连通块中点的数量;

输入格式

第一行输入整数 n 和 m。

接下来 m 行,每行包含一个操作指令,指令为 C a bQ1 a b 或 Q2 a 中的一种。

输出格式

对于每个询问指令 Q1 a b,如果 a 和 b 在同一个连通块中,则输出 Yes,否则输出 No

对于每个询问指令 Q2 a,输出一个整数表示点 a 所在连通块中点的数量

每个结果占一行。

数据范围

1n,m1051≤n,m≤10^5

输入样例:

5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5

输出样例:

Yes
2
3

思路分析:

对于这道题,我们需要多进行一个关于并查集集合数量的一个维护问题,那么我们该怎样来维护呢?

  • 我们可以多开一个数组用来存储数据的数量(sum数组)
  • 每个结点刚开始初始化就是1(因为老光棍就只有一个嘛)
  • 然后对于将两个点合并的时候,我们在合并的同时,将此节点的根节点记录的并查集数量交给合并后的老大(根节点)
  • 所以在查询并查集结点数量的时候就可以直接输出sum的值了

代码如下:

#include<iostream>
using namespace std;

const int N = 100010;

//维护数组
int pre[N], sum[N];
int n, m;

//初始化
void Init() {
	for (int i = 1; i <= n; i++) {
		pre[i] = i;//初始时,自己就是根节点
		sum[i] = 1;//初始时,只有自己一个老光棍,所以sum就是1
	}
}

//查找函数
int find(int x) {
	if (pre[x] != x)pre[x] = find(pre[x]);
	return pre[x];
}

int main()
{
	ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n >> m;

	Init();

	while (m--) {
		string op;
		int x, y;
		cin >> op;
		if (op == "C") {
			cin >> x >> y;
			int fx = find(x);
			int fy = find(y);
			//因为题目说,两点可能在一个集合上,所以要特判一下
			if (fx != fy) {
				pre[fx] = fy;
				sum[fy] += sum[fx];
			}
		}
		else if (op == "Q1") {
			cin >> x >> y;
			if (find(x) == find(y))cout << "Yes" << endl;
			else cout << "No" << endl;
		}
		else {
			cin >> x;
			cout << sum[find(x)] << endl;
		}
	}
	return 0;
}