初学并查集


前言

        1.并查集用于解决一些元素分组的问题。它管理一系列不相交的集合,并支持两种操作:

                a.合并(Union):把两个不相交的集合合并为一个集合。

                b.查询(Find):查询两个元素是否在同一个集合中。

        2.路径压缩

                对于一些指向较长的并查集,(1->2->3->.....->n)它的合并和查找效率极慢(O(N)),而并查集本身并不关注各个集合之间具体的联通关系,换言之,以下两者在运用并查集处理问题时等效

而后者的合并,查询操作的时间仅为O(1);

(自用,相关基础操作就不赘述了)


一、P1551 亲戚

题目描述

规定:x 和 y 是亲戚,y 和 z 是亲戚,那么 x 和 z 也是亲戚。如果 x,y 是亲戚,那么 x 的亲戚都是 y 的亲戚,y 的亲戚也都是 y 的亲戚。

输入格式

第一行:三个整数 n,m,p,(n,m,p≤5000),分别表示有 n 个人,m 个亲戚关系,询问 p 对亲戚关系。

以下 m 行:每行两个数Mi​,Mj​,1≤Mi​, Mj​≤n,表示 Mi​ 和 Mj​ 具有亲戚关系。

接下来 p 行:每行两个数 Pi​,Pj​,询问 Pi​ 和 Pj​ 是否具有亲戚关系。

输出格式

p 行,每行一个 Yes 或 No。表示第 i 个询问的答案为“具有”或“不具有”亲戚关系。

输入输出样例

输入 

6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6

输出 

Yes
Yes
No

 题目分析

        并查集模板题,并查集相关操作见代码


代码样例 

#include <stdio.h>

const int N = 1e5 + 10;

int pre[N];

int root(int x) {
    return pre[x] = (pre[x] == x ? x : root(pre[x])); //包含路径压缩的find操作
}

int main() {
    int n, m, p;
    int t1,t2;
    scanf("%d%d%d", &n, &m, &p);
    for(int i = 1; i <= n; i++) pre[i] = i;
    for(int i = 1; i <= m; i++) {
        scanf("%d%d", &t1, &t2);
        pre[root(t1)] = root(t2); //合并(merge)操作,可单独写函数外置
    }
    for(int i = 1; i <= p; i++) {
        scanf("%d%d", &t1, &t2);
        if(root(t1) == root(t2)) printf("Yes\n"); //如果根节点(祖先)相同,则为亲戚
        else printf("No\n");
    }
    return 0;
}

 

二、P1111 修复公路

题目描述

给出 A 地区的村庄数 N,和公路数 M,公路是双向的。并告诉你每条公路的连着哪两个村庄,并告诉你什么时候能修完这条公路。问最早什么时候任意两个村庄能够通车,即最早什么时候任意两条村庄都存在至少一条修复完成的道路(可以由多条公路连成一条道路)。

输入格式

第 1 行两个正整数 N,M。

下面 M 行,每行 3 个正整数 x,y,t,告诉你这条公路连着 x,y 两个村庄,在时间t时能修复完成这条公路。

输出格式

如果全部公路修复完毕仍然存在两个村庄无法通车,则输出 −1,否则输出最早什么时候任意两个村庄能够通车。

输入输出样例

输入

4 4
1 2 6
1 3 4
1 4 5
4 2 3

输出

5

说明/提示

1≤x,y≤N≤10^{3},1≤M,t≤10^{5}


题目分析

        与模板题不同的是,本题在连接时要考虑应时间而产生的连接顺序;那么可以先根据时间对待连接区块进行排序,再根据时间遍历,进行连通操作。而连通时,由于每两个村庄间可能不止一条道路,对于这些多余的道路不妨直接跳过(对所求结果无影响);而当有效通路数量为 n-1 时,所有村庄便已悉数联通。


代码示例 

#include <cstdio>
#include <iostream>
#include <algorithm>

const int N = 1e5 + 10;

struct node{
    int x, y, t;
}tem[N];

int pre[N];
int t1, cnt;


int cmp(const node &a,const node &b) {
    return a.t < b.t;
    }

int root(int x) {
    return pre[x] = (pre[x] == x ? x : root(pre[x]));
}


int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) pre[i] = i;
    for(int i = 1; i <= m; i++) {
        scanf("%d%d%d", &tem[i].x, &tem[i].y, &tem[i].t);
    }
    std::sort(tem + 1, tem + m + 1, cmp);
    for(int i = 1; i <= m; i++) {
        if(root(tem[i].x) != root(tem[i].y)) { //如果两者之前未联通
            pre[root(tem[i].x)] = root(tem[i].y);
            n--;
        }
        if(n == 1) {
            printf("%d\n", tem[i].t);
            return 0;
        }
    }
    printf("-1\n");
    return 0;
}

  • 30
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值