洛谷题单指南-集合-P1551 亲戚
原题链接:https://www.luogu.com.cn/problem/P1551
题意解读:要判断两人是否是亲戚,只需要看两人是否属于一个集合,基于所有已知的亲戚关系,可以建立多个有亲戚关系的集合,这个过程可以借助并查集。
解题思路:
并查集:
1、定义
并查集是一种树形数据结构,本质上是多棵树,每棵树表示一个集合,主要提供两种操作:
- 查找某个元素属于哪个集合
- 将两个元素所属的集合合并
2、原理
并查集主要采用数组模拟树的双亲表示法,如对于1~5的元素建立并查集,可以定义数组int p[6],数组的值p[i]表示元素i的父节点
初始时,每个元素的值等于自身p[i] = i,说明每个元素的父节点都是自己,各自构成一个集合。
3、查找
当要查找x所属的集合,集合的编号是x所在树的根节点,只需要如下操作:
int find(int x)
{
while(p[x] != x ]) x = p[x];
return x;
}
通常写成递归形式
int find(int x)
{
if(p[x] == x) return x;
return find(p[x]);
}
4、合并
当要将x、y所属的集合合并,只需要如下操作:
void merge(int x, int y)
{
p[find(x)] = find(y);
}
即将x所在集合的根节点的父节点设置为y所在集合的根节点
举例:
初始时1~5的元素形成5个集合
合并2,3,merge(2, 3), 变成4个集合
合并1,2,merge(1, 2), 变成3个集合
在判断1、3是否属于同一个集合时,只需要判断1、3所在集合的根节点是否相同
if(find(1) == find(3))
5、路径压缩
接上图,如果执行merge(4, 1),变成2个集合
在find(4)时,需要先找到p[4] = 1,再找到p[1] = 2,p[2] = 3,如果这个链路很长,每次操作就比较耗时
路径压缩本质上就是在一次find之后,把从4开始向上一条链上的节点的父节点都直接指向根节点3,这样以后再查找元素的集合时只需要一次查找即可
具体来说,就是在find内将递归函数的返回值进行赋值:
int find(int x)
{
if(p[x] == x) return x;
return p[x] = find(p[x]);
}
这样可以保证在find(x)时将x到根节点链路上所有的节点都指向根节点,如find(4)之后:
1/2/4的父节点直接指向3,后续的find操作就比较快了。
并查集的原理介绍到这里,下面实现本题代码。
100分代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 5005;
int p[N];
int n, m, q;
int find(int x)
{
if(p[x] == x) return x;
return p[x] = find(p[x]);
}
void merge(int x, int y)
{
p[find(x)] = find(y);
}
int main()
{
cin >> n >> m >> q;
int i, j;
//初始化
for(int i = 1; i <= n; i++) p[i] = i;
while(m--)
{
cin >> i >> j;
merge(i, j);
}
while(q--)
{
cin >> i >> j;
if(find(i) == find(j)) cout << "Yes" << endl;
else cout << "No" << endl;
}
return 0;
}