并查集的基本原理:四海之内的人,通过祖宗放在关联在一起。
例如,A的祖宗是B,B的祖宗又是C,D的祖宗若是C,则认为A和C就是一个集合的。
也就是说,每个元素有自己的祖宗信息,如果两元素根据现有祖宗信息,向上寻找到的最终祖宗一样,就认为俩元素就是一个集合。
大致框架如下:
#include <iostream>
#include <vector>
class DisjointSet {
public:
std::vector<int> parent;
// 初始化并查集,每个元素的父节点指向自己
DisjointSet(int n) {
parent.resize(n);
for (int i = 0; i < n; ++i) {
parent[i] = i;
}
}
// 查找元素x所属的集合(根节点)
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 路径压缩
}
return parent[x];
}
// 合并元素x和元素y所在的两个集合
void unite(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
parent[rootX] = rootY;
}
}
};
这里说明一下find(x),此处进行了叫“路径压缩”的优化。如果不优化,则A的祖宗是B,但B不是最终祖宗(当parent[x]==x时,证明x为最终祖宗,无法往上再找了),B的祖宗是C。这样找A的最终祖宗会遍历不少结点,浪费时间。路径压缩后,A的祖宗直接修改为C,并返回C,更快捷。
P1. 洛谷p1551亲戚
#include <iostream>
#include <vector>
using namespace std;
class DisjointSet {
public:
std::vector<int> parent;
DisjointSet(int n) {
parent.resize(n+1);
for (int i = 0; i < n+1; ++i) {
parent[i] = i;
}
}
int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
void unite(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
parent[rootX] = rootY;
}
}
};
int main()
{
int n,m,p;
cin>>n>>m>>p;
DisjointSet a=DisjointSet(n);
for(int i=1;i<=m;i++)
{
int p1,p2;
cin>>p1>>p2;
a.unite(p1,p2);
}
for(int i=1;i<=p;i++)
{
int p1,p2;
cin>>p1>>p2;
if(a.find(p1)==a.find(p2)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}