数据结构-并查集专题(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)

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

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

相关文章

算法每日练 -- 双指针篇(持续更新中)

介绍&#xff1a; 常见的双指针有两种形式&#xff0c;一种是对撞指针&#xff08;左右指针&#xff09;&#xff0c;一种是快慢指针&#xff08;前后指针&#xff09;。需要注意这里的双指针不是 int* 之类的类型指针&#xff0c;而是使用数组下标模拟地址来进行遍历的方式。 …

qt QListView详解

1、概述 QListView 是 Qt 框架中的一个视图类&#xff0c;用于展示模型中的数据。它基于 QAbstractItemView&#xff0c;支持多种视图模式&#xff0c;如列表视图&#xff08;List View&#xff09;、图标视图&#xff08;Icon View&#xff09;等。QListView 是模型/视图框架…

数据集市是什么?有什么优势?

一、数据集市是什么&#xff1f; 1、数据集市的产生背景&#xff1a; 因为数据仓库的工作范围和成本比较巨大&#xff0c;技术部门必须对所有的以全企业的眼光对待任何一次决策分析&#xff0c;这样就变成了成本高、耗时高的大项目&#xff0c;而且这种集中式的数据处理方式往往…

apk因检测是否使用代理无法抓包绕过方式

最近学习了如何在模拟器上抓取APP的包&#xff0c;APP防恶意行为的措施可分为三类&#xff1a; &#xff08;1&#xff09;反模拟器调试 &#xff08;2&#xff09;反代理 &#xff08;3&#xff09;反证书检验 第一种情况&#xff1a; 有的app检验是否使用系统代理&#xff0c…

TDengine 签约蘑菇物联,改造通用设备工业互联网平台

在当前工业互联网迅猛发展的背景下&#xff0c;企业面临着日益增长的数据处理需求和智能化转型的挑战。通用工业设备的高能耗问题愈发突出&#xff0c;尤其是由这些设备组成的公辅能源车间&#xff0c;亟需更高效的解决方案来提升设备运行效率&#xff0c;降低能源消耗。为此&a…

Python怎么查看编码

在读取中文的情况下&#xff0c;通常会遇到一些编码的问题&#xff0c;但是首先需要了解目前的编码方式是什么&#xff0c;然后再用decode或者encode去编码和解码&#xff0c;下面是使用chardet库来查看编码方式的。 import chardet path "E:/t.csv" #path "E…

sqoop Oracle 导入到hive 日期时间消失

sqoop脚本&#xff1a; sqoop import -D mapred.job.queue.namehighway \ -D mapreduce.map.memory.mb4096 \ -D mapreduce.map.java.opts-Xmx3072m \ --connect "jdbc:oracle:thin://localhost:61521/LZY2" \ --username LZSHARE \ --password 123456 \ --query &q…

[产品管理-59]:项目组合中产品或项目的类型分类

目录 一、概述 1、突破型项目&#xff1a;全新产品&#xff0c;颠覆性产品 2、平台型项目&#xff1a;平台产品 3、衍生型项目&#xff1a;衍生出来的新产品&#xff0c;不同于现有产品&#xff0c;但与现有产品有关联 4、支持性项目&#xff1a;现有产品的改进&#xff0…

第2章立项-2.4如何进行立项评审?

2.4 如何进行立项评审&#xff1f; 2.4 如何进行立项评审&#xff1f;2.4.1 立项沟通不充分会带来的问题2.4.2 让大家都参与到立项评审中发表意见 2.4 如何进行立项评审&#xff1f; 研发项目不仅仅和研发相关&#xff0c;而且跟采购、工艺、制造、销售、市场都相关。如果相关…

基于vue3实现的聊天机器人前端(附代码)

<template><div class"container"><!-- 页面头部 --><header><h1>跟它说说话吧&#xff01;</h1><p>一个活泼的伙伴&#xff0c;为你提供情感支持&#xff01;</p></header><!-- 聊天容器 --><div c…

【安卓13 源码】Input子系统(4)- InputReader 数据处理

1. 多指触控协议 多指触控协议有 2 种&#xff1a; > A类&#xff1a; 处理无关联的接触&#xff1a; 用于直接发送原始数据&#xff1b; > B类&#xff1a; 处理跟踪识别类的接触&#xff1a; 通过事件slot发送相关联的独立接触更新。 B协议可以使用一个ID来标识触点&…

Windows Server 怎么关闭IE增强安全配置(关闭IE弹窗)

首先第一步打开IE浏览器&#xff0c;根据下图所示&#xff0c;访问网页时会弹出警告窗口。 打开【控制面板】图标。查看方式改为小图标&#xff0c;打开【管理工具】 第五步进入【管理工具】页面后&#xff0c;找到并双击【服务器管理器】选项。 第六步在弹出的窗口中&#…

鸿萌数据迁移服务: 企业服务器整机在线热迁移, 实现不停机业务转移

天津鸿萌科贸发展有限公司从事数据安全服务二十余年&#xff0c;致力于为各领域客户提供专业的数据存储、数据恢复、数据备份、数据迁移等解决方案与服务&#xff0c;并针对企业面临的数据安全风险&#xff0c;提供专业的相关数据安全培训。 鸿萌数据迁移业务为众多企业顺利高效…

ChatGPT 新体验:AI 搜索功能与订阅支付指南

就在凌晨&#xff0c;在 ChatGPT 迎来两周岁生日之际&#xff0c;OpenAI 重磅发布了 ChatGPT 的全新人工智能搜索体验。 期待已久的时刻终于到来&#xff0c; ChatGPT 正式转型成为一款革命性的 AI 搜索引擎&#xff01; 先来看看 ChatGPT 搜索&#xff1a;这次不是简单的加个…

[JAVAEE] 面试题(四) - 多线程下使用ArrayList涉及到的线程安全问题及解决

目录 一. 多线程下使用ArrayList 1.1. 自行判断加锁 1.2 使用Collections.synchronizedList()套壳加锁 1.3 CopyOnWriteArrayList类 二. 总结 一. 多线程下使用ArrayList 多线程下使用ArrayList会涉及到线程安全问题, 例如: public static void main(String[] args) thro…

canal1.1.7使用canal-adapter进行mysql同步数据

重要的事情说前面&#xff0c;canal1.1.8需要jdk11以上&#xff0c;大家自行选择&#xff0c;我这由于项目原因只能使用1.1.7兼容版的 文章参考地址&#xff1a; canal 使用详解_canal使用-CSDN博客 使用canal.deployer-1.1.7和canal.adapter-1.1.7实现mysql数据同步_mysql更…

DevExpress中文教程 - 如何使用AI模型检查HTML编辑中的语法?

DevExpress .NET MAUI多平台应用UI组件库提供了用于Android和iOS移动开发的高性能UI组件&#xff0c;该组件库包括数据网格、图表、调度程序、数据编辑器、CollectionView和选项卡组件等。 目前许多开发人员正在寻找多种方法将AI添加到解决方案中&#xff08;这通常比想象的要…

【推荐】iptables学习宝典

链接&#xff1a; IPtables-朱双印博客 学习iptables的抗鼎之作&#xff0c;推荐。

二维数组和数组指针数组的关系

在深入理解指针end中&#xff0c;我在最后写了一长段代码 #include<stdio.h> void test1(int arr[][5], int x, int y) //void test1(int(*p)[5], int x, int y) {for (int i 0; i < x; i){for (int j 0; j < y; j){//printf("%d ", *(*(p i) j));p…

vue+websocket实现即时聊天平台

目录 1 什么是websocket 2 实现步骤 2.1 导入依赖 2.2 编写代码 1 什么是websocket WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它主要用于在客户端和服务器之间建立持久的连接&#xff0c;允许实时数据交换。WebSocket 的设计目的是为了提高 Web 应用程序的…