个人学习笔记平台,欢迎大家交流

红叶~

Be humble, communicate clearly, and respect others.

并查集小结

并查集

并查集可用来解决一些元素分组的问题,管理一系列不相交的集合

  • 合并:把不相交的集合合并到一个集合
  • 查询:查询两个元素是否在同一集合

原理 起初有1~n个元素,它们分别指向自己,父节点即为自己

当两个元素要合并时,用其中一个元素指向另外一个fa[i] = j

代表 i 的父元素是 j 。
image-20211205114743472

  • 查询
// 查询根节点
int find(int x){
if(fa[x] == x) return x;
else return find(fa[x]);
}
  • 合并
void merge(int i,int j) {
fa[find[i]] = find(j);
// 将i和j的父节点相连
}
  • 路径压缩
// 一句话
int find(int x) {
return x == fa[x] ? x :(fa[x] == find(fa[x]));
}
// 拆分
int find(int x)
{
if(x == fa[x])
return x;
else{
fa[x] = find(fa[x]); //父节点设为根节点
return fa[x]; //返回父节点
}
}

ACwing836合并集合

一共有 n个数,编号是 1∼n,最开始每个数各自在一个集合中。

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

  1. M a b,将编号为 a和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. Q a b,询问编号为 a和 b 的两个数是否在同一个集合中;

模板

#include <iostream>
using namespace std;
const int N = 100010;
int p[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) p[i] = i;
while (m -- )
{
char op[2];
int a, b;
scanf("%s%d%d", op, &a, &b);
if (*op == 'M') p[find(a)] = find(b); // 合并,将a 和 b的父节点相连
else
{
if (find(a) == find(b)) puts("Yes");
else puts("No");
}
}
return 0;
}

自己写的

#include<iostream>
using namespace std;
const int N = 100010, M = 100010;
int fa[N];
// 查找父节点,顺便将路径上的点都指向集合中的根节点
int find(int x) {
if(x == fa[x]) return x;
else fa[x] = find(fa[x]);
return fa[x];
}
// 合并
void merge(int i, int j) {
fa[find(i)] = fa[find(j)]; // 将i和j的父节点链接
}
// 查询是否在同一集合
bool query(int i, int j) {
if(find(i) == find(j)) return true;
return false;
}
int main() {
int n, m;
cin >> n >> m;
for(int i = 1;i <= n;i++) fa[i] = i; // 初始化指向自己
char op;
while(m--) {
int x, y;
cin >> op;
if(op == 'M') {
cin >> x >> y;
merge(x,y);
}else if(op == 'Q') {
cin >> x >> y;
if(query(x, y)) cout << "Yes" << endl;
else cout << "No" << endl;
}
}
return 0;
}

连通块中点的数量

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

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

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

本题主要是要记录连通块中点的数量,初始化一个size数组,当合并的时候更新根节点的size数组就可以了。

自己写的

#include <iostream>
#include <string>
using namespace std;
const int N = 100010;
int p[N], Size[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 合并时同一个集合不一定所有节点指向根节点,但查询时,find函数路径压缩会让一个集合中元素都指向根节点。
void merge(int i, int j) {
int x = find(i);
int y = find(j);
p[x] = y; // y是根节点了
Size[y] += Size[x];
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) {
p[i] = i;
Size[i] = 1;
}
while (m -- )
{
string op;
int a,b;
cin >> op;
if(op == "C") {
scanf("%d%d", &a, &b);
if(find(a) != find(b)) {
merge(a,b); // 不是一个连通块才相连
}
}
else if(op == "Q1") {
scanf("%d%d", &a, &b);
if(find(a) == find(b)) puts("Yes");
else puts("No");
}else {
scanf("%d", &a);
int parent = find(a); // 找到根节点
cout << Size[parent] << endl;
}
}
return 0;
}

模板

#include <iostream>
using namespace std;
const int N = 100010;
int n, m;
int p[N], cnt[N];
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
cnt[i] = 1;
}
while (m -- )
{
string op;
int a, b;
cin >> op;
if (op == "C")
{
cin >> a >> b;
a = find(a), b = find(b);
if (a != b) // 不是同一个连通块的才相连
{
p[a] = b;
cnt[b] += cnt[a];
}
}
else if (op == "Q1")
{
cin >> a >> b;
if (find(a) == find(b)) puts("Yes");
else puts("No");
}
else
{
cin >> a;
cout << cnt[find(a)] << endl;
}
}
return 0;
}

带权并查集

普通的并查集仅仅记录的是集合的关系,这个关系只是同一个集合或不在同一集合。而带权并查集,不仅记录集合的关系,还记录着集合内元素的关系(或者说元素连接线的权值)。

解决这类问题主要是要明确距离在题目中指的是什么,

  • 距离是各个集合中每个元素到根节点之间的距离,当合并两个集合时,根节点也发生变化,所以距离也要更新。
  • 通过画图、数学求出不同元素到根节点之间的联系。假设X、Y是同一集合中的元素,通过X和根节点的关系Y和根节点的关系推出X和Y之间的关系

食物链

image-20211205203015589

posted @   红叶~  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示