洛谷题单指南-集合-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;
}

 

posted @ 2024-03-20 11:30  五月江城  阅读(34)  评论(0编辑  收藏  举报