Powered by:AB_IN 局外人
并查集例题:戳这!!!
- 首先什么是并查集?
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
(摘自百度)
- 是不是觉得很难很抽象?菜鸡一开始也这么觉得,
其实看看标程,自己琢磨一下就懂了。
例题来自 宁波工程学院2020新生校赛(重现赛) L 小梁的道馆-
题目描述:
小梁变强之后决定建设自己的道馆,她特别喜欢去其他的道馆串门。但是有些道馆之间没有道路连通,于是小梁想知道自己能不能去她想去的 道馆。你能帮她写一个程序来查询两个道馆之间是否互相存在道路联通吗?如果存在输出“YES”,反之输出“NO”。 -
输入描述:
第一行为三个整数N为道馆个数,M为线路条数,T为查询次数。第二行至第M+1行,每行两个整数,代表两个道馆的编号 ,表示这两个道馆之间有道路相连。
第M+2行至第 行,每行两个整数,代表查询这两个道馆。
-
输出描述:
T行,每行对应一个查询,假如查询的道馆之间可以连接则输出YES,否则则输出NO。
-
- 好,我们现在来看这道题。道馆可以比作一个个点,一共有N个点。然后给出M条线路,再让你查询T次。
- 一开始我怎么想的呢,我想把每次输入的数变成一个列表,排个序,存在另一个大列表里,然后查找时,看看输入在不在大列表里。(python实现)
- 但其实这么想是错误的。因为如果输入的是
那么就不知道1和3其实也连起来了,只知道1和2连起来,2和3连起来了。
所以这该怎么实现啊? - 为了方便起见,我们举个栗子:
假设N=6
然后M=5
,线路条数分别是:
3 1
2 1
4 6
5 6
1 4
ok,是这个样子的。这个其实对应了第一步(预处理),就是将数组的每个数的值都赋上下标的值。(就是圈圈里填上数字)
好,接下来我们执行我们的3 1
操作。
可以看到我故意 让第一个数指向了第二个数,好,我们暂且让
成为父节点,
成为子节点,可以理解为
好,我们继续
OK,可以看到
也是
的小弟(子节点)了。
接下来就是
和
认
做大哥(父节点)了。
到了比较关键步骤了
好了,图到这里画完了 。看这张图,我们知道
是相连的,但是,其实
、
都是相连的啊,看这张图的箭头我走不过去啊!!
到了关键步骤了!!:核心思想:找老大(父节点)
-
在外面可都是小弟啊!怎么办?回去找老大呗!
-
的老大是 , 的老大是 , 说明 的老大臣服于 的老大了,所以其实就相当于
-
所以真正的图是这样的:
解读一下核心思想:找老大(父节点),我们就可以做题啦!其实判断两个数是否相连,其实就是看他们的父节点是否一致
举个栗子:判断 是否连通?
先看 : 到父节点的路径为
再看 : 到父节点的路径为
不难看出,他们两个的父节点相同,所以他俩连通。
思想我们明白了,那么如何代码实现呢?
- 首先我们定义个数组
fa[1001]
- 预处理(前面说过的)
for(int i = 1;i <= n; i++) fa[i] = i;
- 接下来输入两个数
scanf("%d %d", &a, &b);
- 接下来让这两个小弟找老大!
这里就牵扯到了find
函数
int find(int x) {
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
找老大的过程像是递归,没错,就是递归思想实现!
- 如果还是自己本身的值,有两种可能:
个人练习生(没有老大,之前还没被利用)。- 是最终boss(目前的父节点,如上面例子的最终图中的 )
-
不是自己本身的值:
说明已经在连线中了,用递归不断调用
find
函数,来找到父节点,如果递归中碰到了那个 函数值和下标相等的点,那就是父节点,跳出即可。 -
可能要问了 ,怎么递归啊,怎么赋值啊?
找到两个的父节点,让fa[左父节点]=右父节点
即可,有点数组链表的意思。
u = find(a); v = find(b);
fa[u] = v;
如果晕乎的话,菜鸡再举个栗子:
比如 上面我举的栗子中 :
- 输入
:
用
find
一找发现 就是 的老大, 用find
一找发现 是 的老大。
所以fa[3]=1
- 输入
:我们从前面知道
fa[2]=1
,开始找 的父节点,先发现fa[2]!=2
,执行else
,开始找fa[2]
(也就是1)的父节点,发现fa[1]=1
,那么就return 1
回到上一层,fa[2]=1
,那么就最终return 1
。 同理。
- 最终一步,输入T,有T次查找,前面说了,看两个数是否连通,就是看两个数父节点是否相同。所以:
scanf("%d %d", &a, &b);
u = find(a); v = find(b);
if(u == v) puts("YES");
else puts("NO");
好啦,介绍完成!
完整代码如下,可以边看代码边debug缕一缕思路哦!
#include <bits/stdc++.h>
using namespace std;
int n, m, t, a, b, u, v;
int fa[1005];
int find(int x) {
if(fa[x] == x) return x;
return fa[x] = find(fa[x]);
}
int main() {
scanf("%d %d %d", &n, &m, &t);
for(int i = 1;i <= n; i++) fa[i] = i;
for(int i = 1;i <= m; i++) {
scanf("%d %d", &a, &b);
u = find(a); v = find(b);
fa[u] = v;
}
while(t--) {
scanf("%d %d", &a, &b);
u = find(a); v = find(b);
if(u == v) puts("YES");
else puts("NO");
}
}
完结。
困死了~~