数据结构-并查集专题(1)
一、前言
因为要开始准备年底的校赛和明年年初的ACM、蓝桥杯、天梯赛,于是开始按专题梳理一下对应的知识点,先从简单入门又值得记录的内容开始,并查集首当其冲。
二、我的模板
虽然说是借用了jiangly鸽鸽的板子,但是自己也小做修改了部分(命名啥的,大体内容并没有修改,jiangly鸽鸽yyds)
#include <bits/stdc++.h>struct DisjointSet {int _n;std::vector<int> _fa,_size;DisjointSet(){}DisjointSet(int n){init(n);}void init(int n) {_fa.resize(n);std::iota(_fa.begin(),_fa.end(),0);_size.assign(n,1);}int find(int x) {if(x!=_fa[x]) {_fa[x] = find(_fa[x]);}return _fa[x];}bool same(int x,int y) {return find(x)==find(y);}bool merge(int x,int y) {int fx = find(x);int fy = find(y);if(fx!=fy) {_size[fx]+=_size[fy];_fa[fy] = fx;return true;}return false;}
};
三、并查集介绍
并查集(disjoint set),英文直译过来是不相交的集合。我们中文取名成并查集,是因为这类集合主要具有两个操作:并和查,并即合并两个集合,查即查询集合的某些信息。
3.1 并
如果有学习过树的知识,那么理解并查集就比较轻松了。“并”操作类似于将森林(两棵树)转化为一棵树,我们只需要让其中一个并查集的根节点的父亲结点指向另外一个并查集的根节点即可。当然选择的时候,我们倾向于让深度小的树的根节点指向深度大的树的根节点。不过后续用上路径压缩后,谁指向谁基本没有什么太大的影响。实际写代码中,我们主要的操作就是对一个并查集的根节点进行修改
3.2 查
1.查询某个节点的根节点 2.查询两个节点是否处于同一个并查集中 3.查询一共有几个并查集 4.查询节点数量最多的并查集和它的节点数量 5.查询节点位于自身并查集的深度
3.3 初始化
在没有进行任何合并前,一个并查集的根节点应该为它自身(切记写代码的时候不要忘记并查集的初始化,当然如果用我的模板的话就不会忘记初始化,构造函数已经写好初始化了)
3.4 路径压缩
正常来说,并查集合并的时间复杂度为O(1),而查询的最坏时间复杂度为O(n),常见的最坏情况就是只有左子树或者只有右子树的一棵树的查询。而如果我们在查询时顺带将查询路径上的结点的父节点属性顺带修改为真正的根节点,那么综合下来,时间复杂度将会被均摊成O(logn),这是一个非常优秀的优化。
四、专题训练
以我学校OJ题目展开训练
4.1 题目总览

4.2 1520: 数据结构-并查集-家庭问题

思路
这道题是很典型的用并查集做的题目,先对并查集进行初始化,我们直接把有关系的两人进行合并,如果已经处于同一个并查集中则不做操作。后续题目需要我们查询的是并查集的数量以及并查集中最大的节点数量
参考代码
前面都是并查集模板,注意构造并查集的时候至少把节点数+1(因为用于实现的vetcor下标从0开始,大部分题目的下标是从1开始的),最后查询的时候利用set进行排序输出即可
#include <bits/stdc++.h>
using i64 = long long;struct DisjointSet {int _n;std::vector<int> _fa,_size;DisjointSet(){}DisjointSet(int n){init(n);}void init(int n) {_fa.resize(n);std::iota(_fa.begin(),_fa.end(),0);_size.assign(n,1);}int find(int x) {if(x!=_fa[x]) _fa[x] = find(_fa[x]);return _fa[x];}bool same(int x,int y) {return find(x)==find(y);}bool merge(int x,int y) {int fx = find(x);int fy = find(y);if(fx!=fy) {_size[fy]+=_size[fx];_fa[fx] = fy;return true;}return false;}
};
using pii = std::pair<int,int>;int main() {std::cin.tie(nullptr)->sync_with_stdio(false);int n,k;std::cin >> n >> k;DisjointSet disjointSet = DisjointSet(n+1);for(int i = 0;i<k;i++) {int mem1,mem2;std::cin >> mem1 >> mem2;disjointSet.merge(mem1,mem2);}std::set<pii> st;for(int i = 1;i<=n;i++) {int t = disjointSet.find(i);st.insert(std::make_pair(disjointSet._size[t],t));}std::cout << st.size() << ' ' << (*st.rbegin()).first << '\n';return 0;
}
4.3 1565: 并查集-团伙

思路
这个题由于enemy的存在,需要多维护一个ene[]数组,ene[]数组初始化为0,如果遇到p等于0的时候,分别判断x和y的ene是否为0,如果为0,则代表他们当前没有enemy,则把ene值设置成对方,即x和y分别属于两个并查集中,如果当前存在ene,那么就把自己合并到他们enemy的并查集中,因为敌人的敌人是朋友。至于p等于1的情况,当做正常并查集的合并来做。最后输出并查集的数量(计算父节点等于自身的节点数量)即可
参考代码
#include <bits/stdc++.h>
using i64 = long long;struct DisjointSet {int _n;std::vector<int> _fa,_size;DisjointSet(){}DisjointSet(int n){init(n);}void init(int n) {_fa.resize(n);std::iota(_fa.begin(),_fa.end(),0);_size.assign(n,1);}int find(int x) {if(x!=_fa[x]) {_fa[x] = find(_fa[x]);}return _fa[x];}bool same(int x,int y) {return find(x)==find(y);}bool merge(int x,int y) {int fx = find(x);int fy = find(y);if(fx!=fy) {_size[fy]+=_size[fx];_fa[fx] = fy;return true;}return false;}
};int main() {std::cin.tie(nullptr)->sync_with_stdio(false);int n,m;std::cin >> n >> m;DisjointSet disjointSet = DisjointSet(n+1);std::vector<int> ene(n+1,0);for(int i = 0;i<m;i++) {int t,x,y;std::cin >> t >> x >> y;if(t) {//enemyif(ene[x]) {disjointSet.merge(y,ene[x]);}else {ene[x] = y;}if(ene[y]) {disjointSet.merge(x,ene[y]);}else {ene[y] = x;}}else {//frienddisjointSet.merge(x,y);}}int ans = 0;for(int i = 1;i<=n;i++) {if(disjointSet.find(i)==i) ++ans;}std::cout << ans << '\n';return 0;
}
4.4 1566: 并查集-打击犯罪

思路
题目有点反直觉,我一开始想着从1到k依次进行判断,断开某个关系后可以使得他们中最大的一个危险程度小于等于n/2,然后后来发现写起来很困难。正难则反,因此我们考虑从n开始循环,跑到1结束,每次循环让这个循环变量i这个节点和它有关系并且大于k的j节点进行合并,然后看看是否有危险程度大于n/2的并查集即可。
参考代码
#include <bits/stdc++.h>
using i64 = long long;struct DisjointSet {int _n;std::vector<int> _fa,_size;DisjointSet(){}DisjointSet(int n){init(n);}void init(int n) {_fa.resize(n);std::iota(_fa.begin(),_fa.end(),0);_size.assign(n,1);}int find(int x) {if(x!=_fa[x]) {_fa[x] = find(_fa[x]);}return _fa[x];}bool same(int x,int y) {return find(x)==find(y);}bool merge(int x,int y) {int fx = find(x);int fy = find(y);if(fx!=fy) {_size[fx]+=_size[fy];_fa[fy] = fx;return true;}return false;}
};int main() {std::cin.tie(nullptr)->sync_with_stdio(false);int n;std::cin >> n;std::vector<std::vector<int>> edges(n+1,std::vector<int>());DisjointSet disjointSet = DisjointSet(n+1);for(int i = 0;i<n;i++) {int siz;std::cin >> siz;for(int j = 0;j<siz;j++) {int t;std::cin >> t;edges[i+1].emplace_back(t);}}for(int k = n;k>=1;k--) {for(auto &edge:edges[k]) {if(edge>k) {disjointSet.merge(k,edge);}}if(disjointSet._size[disjointSet.find(k)]>n/2) {std::cout << k << '\n';return 0;}}return 0;
}
4.5 1567: 并查集-搭配购买

思路
并查集+01背包,中间对数据的存储搞得我很头疼,用了一堆vector,先把c[]和d[]数组存起来,做了并查集的合并后,再算一个并查集c[]的总和sumc[]以及d[]的总和sumd[],然后把计算好的总和放进real_c[]和real_d[]数组中,再使用一个dp[]数组做一遍01背包,最后的dp[w]输出即可
参考代码
#include <bits/stdc++.h>
using i64 = long long;struct DisjointSet {int _n;std::vector<int> _fa,_size;DisjointSet(){}DisjointSet(int n){init(n);}void init(int n) {_fa.resize(n);std::iota(_fa.begin(),_fa.end(),0);_size.assign(n,1);}int find(int x) {if(x!=_fa[x]) {_fa[x] = find(_fa[x]);}return _fa[x];}bool same(int x,int y) {return find(x)==find(y);}bool merge(int x,int y) {int fx = find(x);int fy = find(y);if(fx!=fy) {_size[fx]+=_size[fy];_fa[fy] = fx;return true;}return false;}
};
using pii = std::pair<int,int>;int main() {std::cin.tie(nullptr)->sync_with_stdio(false);int n,m,w;std::cin >> n >> m >> w;std::vector<int> c,d;for(int i = 0;i<n;i++) {int tmpc,tmpd;std::cin >> tmpc >> tmpd;c.emplace_back(tmpc);d.emplace_back(tmpd);}DisjointSet disjointSet = DisjointSet(n+1);for(int i = 0;i<m;i++) {int x,y;std::cin >> x >> y;disjointSet.merge(x,y);}std::vector<int> sumc(n+1,0),sumd(n+1,0);for(int i = 1;i<=n;i++) {int t = disjointSet.find(i);sumc[t] += c[i-1];sumd[t] += d[i-1];}std::vector<int> real_c,real_d;for(int i = 1;i<=n;i++) {if(sumc[i]!=0) {real_c.emplace_back(sumc[i]);real_d.emplace_back(sumd[i]);}}std::vector<int> dp(w+1,0);for(int i = 0;i<real_c.size();i++) {for(int j = w;j>=real_c[i];j--) {dp[j] = std::max(dp[j],dp[j-real_c[i]]+real_d[i]);}}std::cout << dp[w] << '\n';return 0;
}
五、后记
剩下的题目及并查集的进一步运用(求最小生成树的Kruskal算法)见后续的(2)
相关文章:
数据结构-并查集专题(1)
一、前言 因为要开始准备年底的校赛和明年年初的ACM、蓝桥杯、天梯赛,于是开始按专题梳理一下对应的知识点,先从简单入门又值得记录的内容开始,并查集首当其冲。 二、我的模板 虽然说是借用了jiangly鸽鸽的板子,但是自己也小做…...
共享汽车管理新纪元:SpringBoot框架应用
4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式,是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示: 图4-1系统工作原理…...
道可云人工智能元宇宙每日资讯|《中国生成式人工智能应用与实践展望》白皮书发布
道可云元宇宙每日简报(2024年11月6日)讯,今日元宇宙新鲜事有: 《重庆市“机器人”应用行动计划(2024—2027年)》发布 近日,重庆市经济和信息化委员会、重庆市教育委员会等八部门印发《重庆市“…...
kaggle学习 eloData项目(1)-数据校验
文章目录 kaggle学习 eloData项目(1)-数据校验(1) 数据基本情况查看(2) 数据校验(3) 数据探究 小结 kaggle学习 eloData项目(1)-数据校验 不能懈怠࿰…...
ORACLE RAC用DNS服务器的配置
一、搭建本地YUM源 二、安装DNS全部组建 yum -y install bind* 三、规划您RAC集群所有IP #public 192.168.16.111 rac1.ntt.com rac1 192.168.16.112 rac2.ntt.com rac2 192.168.16.121 rac3.ntt.com rac3 192.168.16.122 rac4.ntt.com rac4 #private 10.10.10.111 rac1-pr…...
vue3 + vite 实现版本更新检查(检测到版本更新时提醒用户刷新页面)
背景 当一个页面很久没刷新,又突然点到页面。由于一些文件是因为动态加载的,当重编后(如前后端发版后),这些文件会发生变化,就会出现加载不到的情况。进而导致正在使用的用户,点击页面发现加载…...
【CSP】爆零的独特姿势
硝烟散,繁花尽,第一次CSP折戟沉沙。 代码拿回来,花几分钟订正下,就是300分。 然而,实战只有100分,还是偷懒得的幸运,觉得第一题题目太简单懒得用文件IO调试... ... 啥也不说了,上图。…...
Git仓库
Git初始 概念 一个免费开源,分布式的代码版本控制系统,帮助开发团队维护代码 作用 记录代码内容,,切换代码版本,多人开发时高效合并代码内容 如何学: 个人本机使用:Git基础命令和概念 多…...
【科研日常】论文投稿的几大状态
Manuscript Submitted(Submitted to Journal):表示论文已经投稿成功,等待期刊工作人员检查论文格式排版、重复率是否符合要求,符合要求的文章会分配给期刊编辑进行处理。 Awaiting Admin Processing:意为等…...
SSLHandshakeException错误解决方案
1、错误提示 调用Http工具报如下异常信息: cn.hutool.core.io.IORuntimeException: SSLHandshakeException: Received fatal alert: handshake_failure2、查询问题 一开始我以为是代码bug,网络bug甚至是配置环境未生效,找了一大圈…...
python数据结构基础(7)
本节学习最后一种数据结构---图,在很多问题中应用图可以帮助构建思维空间,快速理清思路,解决复杂问题. 图就是一些顶点的集合,这些顶点通过一系列边链接起来.根据边的有向和无向,图分为有向图和无向图.有时图的边上带有权重,本节暂时不将权重作为重点. 计算机通过邻接表或者邻…...
【系统集成项目管理工程师】英语词汇对照表-项目管理类
英语单词(项目管理类)中文解释Activity活动Accept验收Acceptable Quality Level可接受的质量水平Acceptance Standard验收标准Acquisition Plan Review采购计划评审Action处理Active On the Arrow双代号网络图Activity Based Costing (ABC)基于活动的成本…...
购物车-多元素组合动画css
学习 渡一课程 多元素组合动画 练习。 在我们开发购物车功能时,经常会有点击添加按钮,就会有一个小圆点掉进购物车的动画,如下图所示,今天我们通过css来实现。 首先实现多元素组合动画 直接上代码,可以复制到本地使用…...
【计网不挂科】计算机网络期末考试——【选择题&填空题&判断题&简述题】题库(3)
前言 大家好吖,欢迎来到 YY 滴计算机网络 系列 ,热烈欢迎! 本章主要内容面向接触过C的老铁 本博客主要内容,收纳了一部门基本的计算机网络题目,供yy应对期中考试复习。大家可以参考 欢迎订阅 YY滴其他专栏!…...
[ vulnhub靶机通关篇 ] 渗透测试综合靶场 DarkHole:1 通关详解 (附靶机搭建教程)
🍬 博主介绍 👨🎓 博主介绍:大家好,我是 _PowerShell ,很高兴认识大家~ ✨主攻领域:【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 🎉点赞➕评论➕收藏 养成习…...
【LeetCode】移除链表中等于设定值的元素、反转链表
主页:HABUO🍁主页:HABUO 🌜有时候世界虽然是假的,但并不缺少真心对待我们的人🌛 1. 移除链表中设定值的元素 题目:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所…...
Redis - 主从复制
在分布式系统中为了解决单点问题,通常会把数据复制多个副本部署到其他服务器,满⾜故障恢 复和负载均衡等需求。Redis也是如此,它为我们提供了复制的功能,实现了相同数据的多个Redis副 本。复制功能是⾼可⽤Redis的基础,…...
UE5 HLSL 学习笔记
half的取值范围是整形的-60000 到 60000,考虑带宽的情况下使用half vector默认为float4 访问可以.xyzw,也可以.rgba,也可以[index],且顺序可以变,比如说.yzwx 矩阵的获取值的方式 第一个行代表获取第1行第0号元素 第…...
一个简单ASP.NET购物车设计
思路: 创建一个多选列表 在cs文件里初始化购物车会话变量,同,创建一个新的 List<string> 并将其赋值给会话状态中的 "Cart" 键–(利用Session) Session 是一种用于存储用户特定信息的对象,这些信息可…...
双向循环列表
双向循环列表的实现。 根据定义实现。不解释,具体细节看代码。 list.h #pragma once#pragma pack(1)typedef struct _MyListEntry {_MyListEntry* next;_MyListEntry* prev; }MyListEntry;#pragma pack()class MyListClass { public:MyListEntry* m_list0;int m_k…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
应用升级/灾备测试时使用guarantee 闪回点迅速回退
1.场景 应用要升级,当升级失败时,数据库回退到升级前. 要测试系统,测试完成后,数据库要回退到测试前。 相对于RMAN恢复需要很长时间, 数据库闪回只需要几分钟。 2.技术实现 数据库设置 2个db_recovery参数 创建guarantee闪回点,不需要开启数据库闪回。…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
