AcWing:并查集

news2024/10/7 8:31:58

并查集理论基础

并查集的作用是什么:

  1. 将两个集合合并。

  1. 询问两个元素是否在一个集合当中。

如果不使用并查集,要完成上述两个操作,我们需要:

创建一个数组来表示某个元素在某个集合之中,如belong[x] = a,即x元素在a集合之中。

那么完成第二个操作“询问两个元素是否在同一个集合”的时间复杂度为O(1),我们只需:

判断if(belong[x] == belong[y])

但是将两个集合合并的效率取决于两个集合中较短的集合的长度,时间复杂度较大O(len):

len = min(a.size(), b.size()); 再改变belong数组的值。

并查集的作用是,使用近乎O(1)的时间复杂度完成上述两个操作。

并查集基本原理:

每一个集合用一棵树来表示,树根的编号就是整个集合的编号。每个节点存储它的父节点,p[x]表示x的父节点。

如何判断一个节点是不是树根呢?等价于if(p[x] == x)

如何求x的集合编号?while(p[x] != x) x = p[x];

如何合并两个集合?只需在两个集合中的任意一个加一条边即可,即:假设px是x的集合编号,py是y的集合编号,令p[x] = y即可。

这么看求x的集合编号那一步的时间复杂度还是挺高的,如何优化呢?

从x向上一旦找到根节点后,就将寻找路径上的每一个节点都直接指向根节点,这样这条路径上的所有节点在这之后的“求集合编号”的操作的时间复杂度就为O(1)了。于是并查集在经过优化后的时间复杂度就近似于O(1)了(路径压缩)。

AcWing 836. 合并集合

代码实现

定义一个p[N]数组来记录每个节点的父节点是谁。初始时刻每个元素单独成一个集合,其树根就是自己

#include <iostream>

using namespace std;

const int N = 100010;
int p[N];
int n, m;

// 路径压缩
int find(int x){
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main(){
    scanf("%d %d", &n, &m);
    // initial
    for(int i = 1; i <= n; i++){
        p[i] = i;
    }
    
    while(m--){
        char operate[2];
        int a, b;
        scanf("%s %d %d", operate, &a, &b);
        if(operate[0] == 'M'){
            p[find(a)] = find(b);
        }
        else{
            if(find(a) == find(b)) puts("Yes");
            else puts("No");
        }
    }
    return 0;
}

AcWing 837. 连通块中点的数量

在连通块中连边的操作,就相当于将连通块合并。

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;
int p[N], cnt[N];
int n, m;

int find(int x){
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main(){
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++){
        p[i] = i;
        cnt[i] = 1;
    }
    
    while(m--){
        string operate;
        int a, b;
        cin >> operate;
        
        if(operate == "C"){
            cin >> a >> b;
            a = find(a), b = find(b);
            if(a == b) continue;
            cnt[b] += cnt[a];
            p[a] = b;
        }
        else if(operate == "Q1"){
            cin >> a >> b;
            if(find(a) == find(b)) puts("Yes");
            else puts("No");
        }
        else{
            cin >> a;
            cout << cnt[find(a)] << endl;
        }
    }
    return 0;
}

注意我开始定义了全局变量count[N]来记录连通块中点的数量,报错:

a.cpp:18:9: error: reference to 'count' is ambiguous
   18 |         count[i] = 1;
      |         ^~~~~

因为c++的库函数有关键字count,所以会冲突了,模糊不清。改成int cnt[N]后问题解决。

还有一个细节需要注意:

在C操作时,先把a,b的根结点取出来了:a = find(a), b = find(b);,因此接下来是先将集合a接到集合b下再把a的连通块大小加到b上,还是先把a的连通块大小加到b上再操作集合都是可以的,如果大家没有提前一步的处理,就必须要先加连通块大小再操作集合,否则操作完集合后,a和b的根结点将会重叠,导致输出错误。如下:

// accepted
if(operate == "C"){
    cin >> a >> b;
    if(find(a) == find(b)) continue;
    cnt[find(b)] += cnt[find(a)];
    p[find(a)] = find(b);
}

// wrong
if(operate == "C"){
    cin >> a >> b;
    if(find(a) == find(b)) continue;
    p[find(a)] = find(b);
    cnt[find(b)] += cnt[find(a)];
}

AcWing 240. 食物链

并查集可以用来维护很多额外信息,如上一题,维护了每一个连接块的大小。

本题并查集维护一个距离数组d[N]来描述某节点到其父节点的距离,而距离用来表示食物链中的关系。

由于一共只有三类动物A,B,C,其中A吃B,B吃C,C吃A。这里假设根节点是某一种动物a1,离它距离为1的节点代表一种吃a1的动物a2,离根节点距离为2的节点代表一种吃a2的动物a3,离根节点距离为3的节点代表一种吃a3的动物a4(注意这里a4和a1是同一种动物)。

所以对于d[N]中维护的值来说,任意两个模3同余的值所代表的节点都表示同一种动物。

#include <bits/stdc++.h>

using namespace std;

const int N = 50010;
int p[N], d[N];
int n, k;

int find(int x){
    if(p[x] != x){
        // 存一下find(p[x])的值,因为如果这里直接p[x] = find(p[x])的话,
        // p[x]就变成根节点了,下面更新d[x]的语句就失效了
        int t = find(p[x]);
        d[x] += d[p[x]];
        p[x] = t;
    }
    return p[x];
}

int main(){
    scanf("%d %d", &n, &k);
    for(int i = 1; i <= n; i++){
        p[i] = i;
    }
    
    int res = 0;
    while(k--){
        int t, x, y;
        scanf("%d %d %d", &t, &x, &y);
        // 当前的话中x或y比n大,为假
        if(x > n || y > n) res++;
        else{
            int px = find(x), py = find(y);
            if(t == 1){
                // x和y是同类且与前面的话冲突时,为假
                if(px == py && (d[x] - d[y]) % 3 != 0) res++;
                else if(px != py){
                    p[px] = py;
                    d[px] = d[y] - d[x];
                }
            }
            else{
                // x吃y且与前面的话冲突时,为假
                if(px == py && (d[x] - d[y] - 1) % 3 != 0) res++;
                else if(px != py){
                    p[px] = py;
                    d[px] = d[y] + 1 - d[x];
                }
            }
        }
    }
    printf("%d", res);
    return 0;
}

注意这里的 d[x] += d[p[x]]里的d[p[x]]是p[x]到它的父节点的距离。原本d[x]存的也是x到父节点的距离,然后p[x]最后变成根节点 ,d[x]才成了x到根节点的距离。

上面可能会带来疑问,为什么我们只在px == py时才进行res的判断?在px != py时只是将两个集合进行合并操作呢?

因为如果x,y动物不属于同一个集合,那么无论说什么,我们都认为这句话是真的:x,y不属于同一集合有两种可能,一种是x,y其中之一或者两者都是新出现的编号,那么新的编号没有和别的编号构成逻辑,直接联系起来即可。第二种是x,y都是之前出现过的编号,并且属于不同的集合, 这两个集合内部的逻辑关系(吃与被吃)与另一个集合没有任何关系,直接联系起来不会影响整体的逻辑关系

一个细节:

x、y经过运算后的d[x]、d[y]可能为负值,因为当 px != py时, d[x] 和 d[y] 的大小关系是不确定的,因此对 d[px] 的处理可能造成结果为负,后续在再次调用 find(x) 时可能会使 d[x] 为负数。所以这里的判断需要特别注意:

(d[x] - d[y]) % 3 != 0
为什么不能写成:
d[x] % 3 != d[y] % 3

原因就在上面提到,c++中负数模正数还是负数,就拿 -1 % 3 这个例子来说:

   -1 % 3 = (-1) - 3 * (-1 / 3)

在数学中和在计算机中虽然他们的计算过程相同,但是计算结果却有些差异。

所以要用第二种写法的话,要写成:

// wrong
d[x] % 3 != d[y] % 3
// right
(d[x] % 3 + 3) % 3 != (d[y] % 3 + 3) % 3

AcWing 1249. 亲戚

找亲戚,假设两个人互为亲戚,那么这两个人的亲戚都互为亲戚。本题也是一个并查集的应用,它涉及到了集合的合并,并询问两个点是不是属于同一个集合。

#include <iostream>

using namespace std;

const int N = 200010;
int n, m, q;
int p[N];

int find(int x){
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main(){
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++){
        p[i] = i;
    }
    
    while(m--){
        int a, b;
        scanf("%d %d", &a, &b);
        p[find(a)] = find(b);
    }
    
    scanf("%d", &q);
    while(q--){
        int c, d;
        scanf("%d %d", &c, &d);
        if(find(c) == find(d))  puts("Yes");
        else puts("No");
    }
    return 0;
}

这题不能用cin cout输入输出不然会超时。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/390751.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

0201基础-组件-React

1 组件和模块 1.1 模块 对外提供特定功能的js程序&#xff0c;一般就是一个js文件 为什么拆分模块呢&#xff1f;随着业务逻辑增加&#xff0c;代码越来越多&#xff0c;越来越复杂。作用&#xff1a;复用js&#xff0c;简化js&#xff0c;提高js运行效率 1.2 模块化 当应用…

用gdb.attach()在gdb下断点但没停下的情况及解决办法

在python中&#xff0c;如果导入了pwntools&#xff0c;就可以使用里面的gdb.attach(io)的命令来下断点。 但是这一次鼠鼠遇到了一个情况就是下了断点&#xff0c;但是仍然无法在断点处开始运行&#xff0c;奇奇怪怪。 这是我的攻击脚本 我们运行一下。 可以看到其实已经运行起…

计算机网络模型、协议

ARP&#xff08;IP->MAC&#xff09;RARP&#xff08;MAC->IP&#xff09;TFTPHTTPDHCPNATARP&#xff08;IP->MAC&#xff09; 主机建立自己的ARP缓冲区存ARP列表 广播ARP请求&#xff0c;单播ARP响应 RARP&#xff08;MAC->IP&#xff09; 用于无盘工作站&am…

Java分布式全局ID(一)

随着互联网的不断发展&#xff0c;互联网企业的业务在飞速变化&#xff0c;推动着系统架构也在不断地发生变化。 如今微服务技术越来越成熟&#xff0c;很多企业都采用微服务架构来支撑内部及对外的业务&#xff0c;尤其是在高 并发大流量的电商业务场景下&#xff0c;微服务…

[1.#]第一章 计算机系统概述——知识回顾

第一章 计算机系统概述 知识回顾 &#xff08;对于考研408而言&#xff09; 这个章节主要以选择题形式考察。 总的来说&#xff0c;这个章节考察的深度、难度不会太大。另外&#xff0c;这个章节的分值占比是比较低的。 不过&#xff0c;对第一章的学习&#xff0c;有助于我们…

使用 Sublime Text 4 优雅地写C++

使用 Sublime Text 4 优雅地写C 进入sublime官网下载sublime的安装包&#xff08;当然也可以在官网下载页面下载portable版本&#xff0c;不过建议下载默认的setup版本&#xff09; 双击安装包&#xff1a; 应该一会就下载完成了。 此时可以在应用列表看到sublime&#xff1a;…

谈谈 爬虫遇到的 Access denied Error code 1020

这几天在练习爬虫的时候&#xff0c;遇到一个问题&#xff0c; 通过 python 代码从站点中拿到了目标图片的 url &#xff0c; 但是&#xff0c;在持久化到本地时&#xff0c;出现了错误&#xff0c;所有保存下来的图片都报错&#xff1a;文件损坏&#xff0c; 而且&#xff0c;…

【博学谷学习记录】超强总结,用心分享|狂野大数据课程【DataFrame的相关API】的总结分析

操作dataFrame一般有二种操作的方式, 一种为SQL方式, 另一种为DSL方式 SQL方式: 通过编写SQL语句完成统计分析操作DSL方式: 领域特定语言 指的通过DF的特有API完成计算操作(通过代码形式)从使用角度来说: SQL可能更加的方便一些, 当适应了DSL写法后, 你会发现DSL要比SQL更加…

LeetCode:最长回文子串(动态规划)

一、题目 https://leetcode.cn/problems/longest-palindromic-substring/description/ 二、 算法思想 使用动态规划思想解决&#xff0c;如果一个子串是回文的&#xff0c;并且它的左右两边各加上一个字符后仍然是回文的&#xff0c;那么这个子串加上这两个字符后也一定是回文…

浅谈 TCP 握手/数据传输/挥手过程以及 tcpdump 抓包工具使用

前言浅谈 OSITCP三次握手数据传输四次挥手Socket 服务端/客户端通信测试服务端代码客户端代码tcpdump 命令监控命令总结FAQ怎么确认数据包的大小&#xff1f;TCP 拥塞如何避免&#xff1f;如何理解 TCP keep-alive 原理?总结前言 在网络知识体系&#xff0c;TCP 这块的三次握…

【计算机组成原理】指令系统

目录 指令格式 按指令数目分类&#xff1a; 零地址指令 一地址指令 二地址指令 三地址指令 四地址指令 按指令长度分类&#xff1a; 指令字长 机器字长 存储字长 按操作码的长度分类 定长操作码 可变长操作码 定长指令字结构可变长操作码------>拓展操作码指令…

女子举重问题

一、问题的描述 问题及要求 1、搜集各个级别世界女子举重比赛的实际数据。分别建立女子举重比赛总成绩的线性模型、幂函数模型、幂函数改进模型&#xff0c;并最终建立总冠军评选模型。 应用以上模型对最近举行的一届奥运会女子举重比赛总成绩进行排名&#xff0c;并对模型及…

Java分布式事务(二)

文章目录&#x1f525;分布式事务处理_认识本地事务&#x1f525;关系型数据库事务基础_并发事务带来的问题&#x1f525;关系型数据库事务基础_MySQL事务隔离级别&#x1f525;MySQL事务隔离级别_模拟异常发生之脏读&#x1f525;MySQL事务隔离级别_模拟异常发生之不可重复读&…

信息安全与数学基础-笔记-②同余

知识目录同余完全剩余系剩余类完全剩余系❀简化剩余系❀欧拉函数逆元&#xff01;欧拉定理 &#xff01;同余 a,b 两个数字&#xff0c;都模m&#xff0c;当两个数字模m后余的数一样即为同余。 例子&#xff1a; a bq r (mod m)&#xff0c;这里的a 和 r 就是同余 &#xff…

如何使用Unity3d实现多人对战联机游戏

所需资源 课程来源&#xff08;请支持正版课程&#xff09; 安装Unity Hub 安装Visual Studio 角色模型 环境准备 ①Unity设置 不设置的话编写有些代码没有自动补全 点开 Preferences 选择 visual studio ②角色导入 点击 windows—>Package Manager 左上角 My Ass…

数据结构与算法(七):排序算法

排序算法是《数据结构与算法》中最基本的算法之一&#xff0c;排序算法可以分为内部和外部排序。 内部排序&#xff1a;数据记录在内存中进行排序。 外部排序&#xff1a;因排序的数据很大&#xff0c;一次不能容纳全部的排序记录&#xff0c;在排序过程中需要访问外存。 常…

xgboost:分割Sparsity-aware Split Finding

Sparsity-aware Split Finding1 在许多现实问题中&#xff0c;输入xxx是稀疏的是很常见的。造成稀疏性的可能原因有很多: 1)数据中存在缺失值&#xff1b; 2)统计中频繁出现零项&#xff1b; 3)特征工程的处理结果&#xff0c;如独热编码。 重要的是使算法意识到数据中的稀…

RocketMQ5.1.0单机安装与启动

RocketMQ单机安装与启动系统要求下载地址安装步骤RocketMq启动NameServer查看是否启动成功启动BrokerProxy查看是否启动成功修改tool.sh测试消息产生消息的消费关闭服务器系统要求 下载地址 官网下载地址 二进制包是已经编译完成后可以直接运行的&#xff0c;源码包是需要编译…

javaWeb核心02-RequestResponse

文章目录Request&Response1&#xff0c;Request和Response的概述2&#xff0c;Request对象2.1 Request继承体系2.2 Request获取请求数据2.2.1 获取请求行数据2.2.2 获取请求头数据2.2.3 获取请求体数据2.2.4 获取请求参数的通用方式基于上述理论&#xff0c;request对象为我…

python:使用 Jupyter notebook(测试 matplotlib 和 opencv)

环境&#xff1a; window1python 3.10.6 参考&#xff1a; https://jupyter.org/https://opencv.org/ 一、创建虚拟环境 这个步骤可以跳过&#xff08;因为笔者不喜欢在全局环境安装任何东西&#xff0c;所以搞一个新环境&#xff09;。 先选中一个目录&#xff1a;D:\jackl…