并查集入门

问题简述

模板题
在编程中,我们经常遇到一类问题。如给定 n n n 个人, m m m 组关系,每组关系给你两个编号 i , j i,j ij;表示 i i i j j j 是朋友。同时如果 x x x y y y 的朋友且 y y y z z z 的朋友,那么 x x x z z z 的朋友

最后询问 q q q 次,每次给 a , b a,b a,b,求 a a a 是否是 b b b 的朋友。是输出 Y,否则输出 N

样例输入及输出

输入
4 5
1 2
2 4
3 2
1 3
1 4
2
1 2
1 3
输出
Y
Y

分析

判断

我们可以先讲互相间有直接朋友关系的用边连接起来。显而易见,如果两个点是联通的,那么这两个点为朋友关系,输出 Y,否则输出 N
在这里插入图片描述
假设我们有一种方式能按先后顺序删除无用的(及所连接的两个点之前已经是联通的),那么我们将它上去。这时,我们的图变为
在这里插入图片描述

因为共有 n − 1 n - 1 n1 条边,所以显而易见图已经变为一棵树,这时我们将图用有向图表示
在这里插入图片描述
在树中,每个节点都有自己唯一的一个父亲,因此我们用 head[i] 来表示第 i i i 个点的父亲,同时根节点的父亲表示为他自己。如果题目是这样:
在这里插入图片描述
这时,我们要判断两个点是否想通,只需要判断两个节点的根节点相同即可。因此,我们可以写出函数 find(i) 表示 i i i 的根节点。具体代码如下:

int find(int x) {
	//如果找到根节点(父亲是自己)
	if(head[x] == x) {
		return x;
	}
	else return find(head[x]);//找父亲
}

你也可以
将它写成:

int find(int a) {
	return a == head[a]?a:head[a] = find(head[a]);
}
输出代码
	scanf("%d",&c);
	while(c--) {
		scanf("%d%d",&d,&e);
		if(find(d) == find(e)) {
			printf("Y\n");
		}
		else{
			printf("N\n");
		}
	}

合并

在这里插入图片描述

观察这张图,我们可以发现此时如果要直接合并点 4 , 5 4,5 4,5,就要把 head[5] = 4head[4] = 5。此时无论如何都要丢失之前的一部分信息。

因此,合并 d , e d,e d,e 时,我们要先找到 e e e 的祖先,再把 head[e 的祖先] =d。为了方便,我们把 head[e 的祖先] =d 的祖先,即 head[find(e)] = find(d)

合并部分代码
	for(int i = 1;i <= b;i++) {
		scanf("%d%d",&d,&e);
		head[find(e)] = find(d); 
	}

完整代码

#include<bits/stdc++.h>
using namespace std;
int c,d,e;
int head[10005];
//int find(int a) {return a == head[a]?a:head[a] = find(head[a]);}
int find(int x) {
	if(head[x] == x) {
		return x;
	}
	else return find(head[x]);
}
int main() {
	int a,b;
	scanf("%d%d",&a,&b);
	for(int j = 1;j <= a;j++) {
		head[j] = j;
	}
	for(int i = 1;i <= b;i++) {
		scanf("%d%d",&d,&e);
		head[find(e)] = find(d); 
	}
	scanf("%d",&c);
	while(c--) {
		scanf("%d%d",&d,&e);
		if(find(d) == find(e)) {
			printf("Y\n");
		}
		else{
			printf("N\n");
		}
	} 
}

优化

因为在出题时出题人可能出现构造特殊情况,如图:
在这里插入图片描述
这种情况(退化成链表),我们每一次查询就要运算 n n n 次,显然是不可取的。因此我们更新 find 程序,每次查找时将路径上说有点的父亲都变成根节点。

inline int find(int a) {return a == head[a]?a:head[a] = find(head[a]);}

Map 的用法

介绍

在 c++ 中,map 通常用来解决数组下标只能是整数的问题。在 map 中,数组下标可以是任意类型。

用法

定义一个下标用 string 表示的字符串 head map<string,string>head;

abcdefg 赋值给第 abc 项:head[abc] = abcdefg

与并查集的关联

我们可以用 map 来储存并查集的每一个项,例如这道题:一中校运会之百米跑

很明显,将一大堆 string 给换成不同的 int 十分麻烦,我们可以直接基于 map 进行类似的并查集操作

代码

#include<bits/stdc++.h>
using namespace std;
int n,m,k; 
map<string,string>head;
string find(string x) {
	return head[x] == x?x:head[x] = find(head[x]);
}
int main() {
	scanf("%d%d",&n,&m);
	while(n--) {
		string a;
		cin>>a;
		head[a] = a;
	}
	while(m--) {
		string a,b;
		cin>>a>>b;
		if(find(a) != find(b)) {
			head[find(b)] = find(a);
		}
	}
	scanf("%d",&k);
	while(k--) {
		string a,b;
		cin>>a>>b;
		if(find(a) == find(b)) {
			printf("Yes.\n");
		}
		else{
			printf("No.\n");
		}
 	}
}

例题

并查集+筛质数 AC记录

纯并查集 AC记录

  • 20
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值