并查集是一种松散的数据结构,在求连通问题时很好用.
题目:P3367 【模板】并查集
思路#
首先,我们假定每一个集合都有一个节点作为代表,我们称那个节点是集合内其余节点的为领导节点.
我们定义一个数组 ,其中, 表示的意义为:若 ,即节点 是一个领导节点,则 表示节点 的领导节点.若 ,则节点 的领导节点就是节点 的领导节点.
我们可以使用递归来求一个节点 的领导节点,在求的同时,我们可以将求到的值保存到 中,下次只需要使用 的复杂度就可以求解了,这个行为叫做路径压缩.
// 查找节点i的领导节点,并进行路径压缩
int find(int t)
{
if (f[t] != t)
f[t] = find(f[t]);
return f[t];
}cpp我们很容易就可以想到,对于两个节点 和 ,如果想要合并,则可以让 设定为 的领导节点.如果想要判断是否在同一集合中,只需要判断节点 和 的领导节点是否相同即可.
// 查询两个节点是否属于同一节点
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 函数的复杂度看似好像是最坏 的,但是由于有了路径压缩,每一次的 find 都会大大减少下一次 find 相同节点的时间,最终无限趋近于 ,但常常带有较大的常数.
因此,查询和合并的复杂度也是 ,综合一下, 次操作的总复杂度约为 .
标程#
#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;
}cpp