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

红叶~

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 @ 2022-02-15 23:32  红叶~  阅读(29)  评论(0)    收藏  举报