算法------(11)并查集

例题:

(1)Acwing 836.合并集合

        并查集就是把每一个集合看成一棵树,记录每个节点的父节点。合并集合就是把一棵树变成另一棵树的子树,即把一棵树的父节点变为另一棵树的父节点的儿子。查询是否在同一集合就是看他们的根节点是否相同。在查找过程中可以路径加速,把每个点直接连到根节点。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
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;}for(int i = 0;i<m;i++){char op[2];int x,y;scanf("%s%d%d",op,&x,&y);if(op[0] == 'M'){p[find(x)] = find(y);}else{if(find(x)==find(y)) printf("Yes\n");else printf("No\n");}}return 0;
}

(2) Acwing 837.连通块中点的数量

         并查集板子题,把每个联通块当成一棵树,连边操作就等于将联通块合并,查询是否在同一个联通块中就等于查询其根节点是否相同,询问数量需要额外维护一个size数组,每次合并时根节点的size要加上被合并的根节点的size。如果合并时两个节点的根节点相同则无需合并,否则size会加倍。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int sz[N],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;sz[i] = 1;}for(int i = 0;i<m;i++){char op[2];int x,y;scanf("%s",op);if(op[0] == 'C'){scanf("%d%d", &x, &y);if(find(x)!=find(y)){sz[find(x)] += sz[find(y)];p[find(y)] = find(x);}}else if(op[1] == '1'){scanf("%d%d", &x, &y);if(find(x) == find(y)) printf("Yes\n");else printf("No\n");}else{scanf("%d", &x);printf("%d\n",sz[find(x)]);}}return 0;
}

(3)AcWing 240. 食物链

        由于是一个环形食物链,因此可以使用带权并查集,记录每个节点到根节点的距离,通过%3所得的数判断两者间的关系。具体代码有注释。 

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int d[N],p[N];
int find(int x){if(p[x]!=x){int t = find(p[x]);//把该点的父节点以上的所有节点全部连到根节点d[x] += d[p[x]];//此时d[p[x]]就是p[x]到根节点的距离p[x] = t;//p[x]连到根节点}return p[x];
}
int main()
{int n,m,res = 0;scanf("%d%d", &n, &m);for(int i = 1;i<=n;i++){p[i] = i;}while (m -- ){int s,x,y;scanf("%d%d%d",&s, &x, &y);if(x>n||y>n){res++;continue;}else if(s == 1){int px = find(x),py = find(y);if(px == py){if((d[x]-d[y])%3) res++;//同类则模0continue;}else{p[px] = py;d[px] = d[y] - d[x];}}else{int px = find(x),py = find(y);if(px == py){if((d[x]-d[y]-1)%3) res++;//可能存在一正一负因此不能==1continue;}else{p[px] = py;d[px] = d[y] - d[x] + 1;}}}printf("%d",res);return 0;
}

练习:(1)Leetcode 128.最长相关序列

         把数组里的每一个数看做一个节点,而每个节点可以与权值比自己本身权值小1的节点相连。对于数组中的每个数,如果权值比他小1的数在哈希表中存在并且两者不具有相同的根结点时,由于数组中不存在重复元素,且每次只会把大的数接在小的数后面,因此两者集合必然是连续的。所以先进行去重然后开始建树,由于存在负数因此利用哈希表储存每个数的下标防止访问越界。问题可以转化为最大的联通块中点的数量。

class Solution {
public:unordered_map<int,int> x;int p[100010],sz[100010];int find(int x){if(p[x]!=x) p[x] = find(p[x]);return p[x];}int longestConsecutive(vector<int>& nums) {sort(nums.begin(),nums.end());nums.erase(unique(nums.begin(),nums.end()),nums.end());int n = nums.size();for(int i = 1;i<=n;i++){x[nums[i-1]] = i;p[i] = i;sz[i] = 1;}for(int i = 0;i<n;i++){if(x[nums[i]-1]&&find(x[nums[i]-1]) != find(x[nums[i]]) ){sz[find(x[nums[i]-1])] += sz[find(x[nums[i]])];p[find(x[nums[i]])] = find(x[nums[i]-1]);}}int ans = 0;for(int i = 0;i<n;i++){if(p[x[nums[i]]] == x[nums[i]]) ans = max(ans,sz[x[nums[i]]]);}return ans;}
};

(2)Leetcode 684.冗余连接

        没做出来。。

        遍历每条边,如果该边的两个端点属于一个集合内,那么就说明这条边导致了一个环的构成。返回这条边。否则把两个端点加到同一个集合内。由于题目规定只会多出一条边(只会存在一个环),因此出现的这条边一定是构成环的最后一条边,因为假如后面还存在能构成环的边则不满足只有一个环的条件,因此这条边就是构成环的最后一条边。

class Solution {
public:int p[1010];int find(int x){if(p[x]!=x) p[x] = find(p[x]);return p[x];}vector<int> findRedundantConnection(vector<vector<int>>& edges) {int n = edges.size();for(int i = 1;i<=n;i++) p[i] = i;for(auto c:edges){int a = find(c[0]),b = find(c[1]);if(a==b) return{c[0],c[1]};else{p[a] = b;}}return {0,0};}
};

(3)Leetcode 1202.交换字符串中的元素

        并查集应该不是最佳解。。不过没想出来啥别的方法。。

        第一步:把所有能交换的位置构成一个集合。第二步:遍历字符串,把每个字母加入其根节点映射的优先队列(可以自动按字典序排序)第三步:遍历字符串,用一个空字符串记录结果。在每个字母对应的位置放上其根节点对应的优先队列中第一个字母(按字典序最小的字母),然后该元素出队。

class Solution {
public:int p[100010];int find(int x){if(p[x]!=x) p[x] = find(p[x]);return p[x];}string smallestStringWithSwaps(string s, vector<vector<int>>& pairs) {unordered_map<int,priority_queue<int,vector<int>,greater<int>>> x;int n = s.length();for(int i = 0;i<n;i++) p[i] = i;for(auto c:pairs){int a = find(c[0]),b = find(c[1]);if(a!=b) p[a] = b;}   for(int i = 0;i<n;i++) x[find(i)].push((int)s[i]);string res = "";for(int i = 0;i<n;i++){char ch = (char)x[find(i)].top();x[find(i)].pop();res+=ch;}return res;}
};

(4)Leetcode 886.可能的二分法

        判断二分图的最好方法并不是并查集,但是。。用并查集没做出来。。

        把每一个人的关系不好的人放入一个集合中,如果这个集合的人里面有人跟该人的根节点相同说明其属于同一个集合,不成立,反之则把他们加入同一个集合。

class Solution {
public:unordered_map<int,vector<int>> x;int p[2010];int find(int x){if(p[x]!=x) p[x] = find(p[x]);return p[x];}bool possibleBipartition(int n, vector<vector<int>>& dislikes) {if(!dislikes.size()) return true;for(auto c:dislikes){x[c[0]].push_back(c[1]);x[c[1]].push_back(c[0]);}for(int i = 1;i<=n;i++) p[i] = i;for(int i = 1;i<=n;i++){if(x[i].empty()) continue;int a = find(i),b = find(x[i][0]);for(auto c:x[i]){if(find(c) == a) return false;p[find(c)] = b;}}return true;}
};

(5) P8686 [蓝桥杯 2019 省 A] 修改数组

         。。。其实还是没太理解。。

        首先把每一个数的根节点设为其本身,然后对于每次输入的数,如果其根节点为本身,说明其第一次出现,因此把根节点更新为原先的根节点+1,否则查找其根节点(由于每次都加1,因此 从 其本身 到 其根节点-1 都已经出现过),然后输出根节点并且把 根节点+1 作为根节点的新父节点(这个父节点有可能还会有父节点,但并不影响,可以理解为两条链相连成为一条更长的新链)

        初始化时要把后面的节点一起初始化,否则更新时由于后续某些节点的根节点变为0就会出错。。

#include <iostream>
using namespace std;
const int N = 100010;
typedef long long ll;
ll p[N];
ll find(ll x){if(p[x]!=x) p[x] = find(p[x]);return p[x];
} 
int main(int argc, char** argv) {ll n;scanf("%d",&n);for(int i = 1;i<=100010;i++) p[i] = i;for(int i = 1;i<=n;i++){ll x;scanf("%lld",&x);printf("%lld ",find(x));p[find(x)] = find(x) + 1;}return 0;
}

(7)洛谷 P1111 修复公路

        。。很烦。。        

        联通块的个数为1时就能完全通车。首先按照时间排序(分清路的条数和村庄的个数!!),然后遍历每一条路,假如两个路边上的村庄共用一个根节点则无视,否则联通块数-1,把两个村庄联通。每次判断所有联通块是否只剩一个,如果是则输出当前时间戳,如果最后还剩不止一个联通块则输出-1。这里判断联通块个数不需要每一次都遍历,只需要维护联通块个数即可(因为一开始每一个村庄就是一个联通块)

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010,M = 100010;
struct Node{int x,y,t; bool operator< (Node &a){return t < a.t;}
}road[M];
int p[N];
int find(int x){if(p[x]!=x) p[x] = find(p[x]);return p[x];
}
int main(int argc, char** argv) {int n,m;scanf("%d%d",&n,&m);int res = n; for(int i = 1;i<=n;i++) p[i] = i;for(int i = 0;i<m;i++){scanf("%d%d%d",&road[i].x,&road[i].y,&road[i].t);}sort(road,road+m);bool flag = 0;for(int i = 0;i<m;i++){int a = find(road[i].x),b = find(road[i].y),c = road[i].t;if(a == b) continue;else{res--;if(res==1){printf("%d",c);flag = 1;break;}p[a] = b;}}if(!flag) printf("-1");return 0;
}

(8)洛谷 P1455 搭配购买

        一个缝合题,01背包加并查集。。01背包写的很差,还是不太会用滚动数组。。后面再补一下吧。。

        用并查集把多个商品合成为一个商品,根节点代表商品本身。01背包不用滚动数组会MLE。更新时注意是更新根节点的价格和价值(因为是连接根节点)。

#include <iostream>
using namespace std;
const int N = 1e4+10;
int w[N],v[N],p[N],f[N];
struct Node{int w,v;
}shop[N];
int find(int x){if(p[x]!=x) p[x] = find(p[x]);return p[x];
}
int main(int argc, char** argv) {int n,m,w1;scanf("%d%d%d",&n,&m,&w1);for(int i = 1;i<=n;i++){scanf("%d%d",&w[i],&v[i]);p[i] = i;}for(int i = 0;i<m;i++){int a,b;scanf("%d%d",&a,&b);a = find(a),b = find(b);if(a==b) continue;p[a] = b;w[b] += w[a];v[b] += v[a];}int cnt = 1;for(int i = 1;i<=n;i++){if(p[i]!=i) continue;shop[cnt].w = w[i];shop[cnt].v = v[i];cnt++;}for(int i = 1;i<=n;i++){for(int j = w1;j>=shop[i].w;j--){f[j] = max(f[j],f[j-shop[i].w] + shop[i].v);}}printf("%d",f[w1]);return 0;
}

(9)P3420 SKA-Piggy Banks

        虽然做出来了。。但是感觉还是搞不太明白。。

        这道题之所以可以用并查集的原因并不是因为罐子之间存在钥匙联系,而是一个罐子只有一个钥匙且在另一个罐子里,但一个罐子可以装多把钥匙。因此我们每次把钥匙对应的罐子连在存放钥匙的罐子后面,则打开根节点的罐子就可以打开所有罐子。因此一个集合内的罐子就一定只需要打开一个罐子,题目也就改为求联通块的数目。因此这道题用并查集可以做的原因其实是每个钥匙唯一且对应了一个罐子。假如题目改为第i个数代表第i个罐子存放了第ai个罐子的钥匙,这道题就不能用并查集做了。。https://www.luogu.com.cn/discuss/684322icon-default.png?t=N7T8https://www.luogu.com.cn/discuss/684322        这是比较专业的解答。。。我只能用自己的角度去解释。。解释的不太好。。

(10)P1196 [NOI2002] 银河英雄传说

        。。确实是挺难的。。

        为了查询两个战舰之间的战舰数目,我们维护每一个战舰到根节点的距离,这样两个战舰之间的战舰数目就是到根节点的距离差-1。而由于我们维护到根节点的距离还需要知道当前集合内的战舰数量,因此还需要维护一个数组用来记录每个集合(根节点下)的战舰数。当然,由于每个节点都没法在合并时就被更新,因此我们可以在find函数时对每个节点进行更新。 

#include <iostream>
using namespace std;
const int N = 30010;
int p[N],dis[N],num[N];
int find(int x){if(p[x]!=x){int k = p[x]; p[x] = find(p[x]);dis[x] += dis[k];num[x] = num[p[x]];}return p[x];
}
int main(int argc, char** argv) {int t;scanf("%d",&t);for(int i = 1;i<=30000;i++){p[i] = i;num[i] = 1;}while(t--){char op[2];scanf("%s",op);int x,y;scanf("%d%d",&x,&y);if(op[0] == 'M'){x = find(x);y = find(y);if(x!=y){p[x] = y;dis[x] = num[y];num[y] += num[x];num[x] = num[y];}}else{if(find(x) == find(y)) printf("%d\n",abs(dis[x] - dis[y])-1);else printf("-1\n");}}return 0;
}

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

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

相关文章

02.数据结构

一、单链表 作用&#xff1a;用于写邻接表&#xff1b; 邻接表作用&#xff1a;用于存储图或树&#xff1b; 1、用数组模拟单链表 #include<iostream> using namespace std;const int N 100010;// head 表示头结点的下标 // e[i] 表示结点i的值 // ne[i] 表示结点i的…

【OrangePi Zero2的系统移植】交叉编译工具链配置、wiringOP库、智能分类工程代码

一、交叉编译工具链配置 二、交叉编译wiringOP库 三、交叉编译智能分类工程代码 四、Makefile 用于编译 WiringPi 库 一、交叉编译工具链配置 1、关于编译 编译是指将源代码文件&#xff08;如C/C文件&#xff09;经过预处理、编译、汇编和链接等步骤&#xff0c;转换为可执…

开发JSP自定义标记

开发JSP自定义标记 您已经学习了如何用JavaBean处理JSP页面的业务逻辑。除此以外,您还可以用自定义标记处理JSP应用程序中反复出现的业务逻辑要求。 tag是程序中使用的执行重复性任务的可重用单元。例如, 是使主体文本在网页中间出现的HTML标记。JSP可用于创建于XML标记类似…

【Rust】——猜数游戏

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

【数据结构与算法】【小白也能学的数据结构与算法】递归 分治 迭代 动态规划 无从下手?一文通!!!

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《数据结构与算法&#xff1a;初学者入门指南》&#x1f4d8;&am…

例37:爱好选择

建立一个新的EXE工程&#xff0c;放两个单选&#xff0c;两个复选框如图33。 图33 输入代码&#xff1a; Sub Form1_Check1_BN_Clicked(hWndForm As hWnd, hWndControl As hWnd)Text1.Text ""If Check1.Value ThenText1.Text"你喜欢" & Check1.Cap…

Linux运行级别 | 管理Linux服务

Linux运行级别 级别&#xff1a; 0关机1单用户2多用户但是不运行nfs网路文件系统3默认的运行级别&#xff0c;给一个黑的屏幕&#xff0c;只能敲命令4未使用5默认的运行级别&#xff0c;图形界面6重启切换运行级别&#xff1a; init x管理Linux服务 systemctl命令&#xf…

鸿蒙实战开发-全局UI方法的功能

主要开发内容 时间调节 使用全局UI的方法定义日期滑动选择器弹窗并弹出。 操作说明&#xff1a;首先创建一个包含按钮的用户界面&#xff0c;当用户点击“时间设置”按钮时&#xff0c;会弹出调用TimePickerDialog组件的show方法&#xff0c;显示一个时间选择对话框&#xff…

[word] word表格内容自动编号 #经验分享#微信#其他

word表格内容自动编号 在表格中的内容怎么样自动编号&#xff1f;我们都知道Word表格和Excel表格有所不同&#xff0c;Excel表格可以轻松自动编号&#xff0c;那么在Word表格中如何自动编号呢&#xff1f; 1、选中内容后&#xff0c;点击段落-自动编号&#xff0c;选择其中一…

vscode远程连接失败

目录 解决方案尝试1解决方案尝试2 解决方案尝试1 最近通过vscode一直使用腾讯云的服务器作为远程开发环境&#xff0c;以前一直很好用。 直到最近重装了系统之后&#xff0c;发现vscode没法对云服务器进行连接了&#xff0c;即使在远程主机添加了本地的公钥也不行。直接报错:…

ChatGpt报错:We ran into an issue while authenticating you解决办法

在登录ChatGpt时报错&#xff1a;Oops&#xff01;,We ran into an issue while authenticating you.(我们在验证您时遇到问题)&#xff0c;记录一下解决过程。 完整报错&#xff1a; We ran into an issue while authenticating you. If this issue persists, please contact…

qt-C++笔记之判断一个QLabel上有没有load图片

qt-C笔记之判断一个QLabel上有没有load图片 code review! 在Qt框架中&#xff0c;QLabel是用来显示文本或者图片的一个控件。如果你想判断一个QLabel控件上是否加载了图片&#xff0c;你可以检查它的pixmap属性。pixmap属性会返回一个QPixmap对象&#xff0c;如果没有图片被加…

error: object ‘FastMNNIntegration‘ not found

加载一个包即可 library(SeuratWrappers) #运行fastmnn之前&#xff0c;需要加载&#xff0c;否则报错 obj <- IntegrateLayers(object obj, method FastMNNIntegration,new.reduction "integrated.mnn",verbose FALSE )

C#系列-使用 Minio 做图片服务器实现图片上传 和下载(13)

1、Minio 服务器下载和安装 要在本地安装和运行 MinIO 服务器&#xff0c;你可以按照以下 步骤进行操作&#xff1a; 1. 访问 MinIO 的官方网站&#xff1a;https://min.io/&#xff0c;然后 点击页面上的”Download”按钮。 2. 在下载页面上&#xff0c;选择适合你操作系统的 …

Docker 有哪些常见的用途?

Docker 是一种容器化技术&#xff0c;它允许应用程序在不同的环境之间具有一致的运行环境。这使得 Docker 在开发和运维领域中非常受欢迎&#xff0c;因为它简化了应用程序的部署和管理。以下是 Docker 的一些常见用途&#xff1a; 快速部署应用程序 Docker 允许开发人员和运…

Hive窗口函数详解

一、 窗口函数知识点 1.1 窗户函数的定义 窗口函数可以拆分为【窗口函数】。窗口函数官网指路&#xff1a; LanguageManual WindowingAndAnalytics - Apache Hive - Apache Software Foundationhttps://cwiki.apache.org/confluence/display/Hive/LanguageManual%20Windowing…

【Android】使用Android Studio运行Hello World项目

文章目录 1. JDK的安装与配置2. Android Studio的安装3. 运行Hello World项目3.1 新建项目3.2 修改项目配置3.2.1 修改UI界面3.2.2 配置 Android SDK 3.3 添加并运行虚拟设备3.4 运行项目 1. JDK的安装与配置 想要使用Android Studio&#xff0c;必须先配置Java环境&#xff0…

hook函数——useRef

useRef useRef 是一个 React Hook&#xff0c;它能帮助引用一个不需要渲染的值。也就是说useRef可以存储一个值&#xff0c;但是不被组件渲染&#xff0c;仅仅只是引用&#xff0c;主要包括两个方面&#xff0c;例如使用ref引用一个值&#xff0c;使用ref引用一个dom节点&…

软件价值12-射箭游戏

射箭游戏&#xff0c;按空格键发射&#xff0c;打击移动靶&#xff0c;左上角显示成绩状态。 代码&#xff1a; import pygame import sys import random# 初始化Pygame pygame.init()# 设置窗口大小 SCREEN_WIDTH 800 SCREEN_HEIGHT 600 screen pygame.display.set_mode((…

【Linux】信号概念与信号产生

信号概念与信号产生 一、初识信号1. 信号概念2. 前台进程和后台进程3. 认识信号4. 技术应用角度的信号 二、信号的产生1. 键盘组合键2. kill 命令3. 系统调用4. 异常&#xff08;1&#xff09;观察现象&#xff08;2&#xff09;理解本质 5. 软件条件闹钟 一、初识信号 1. 信号…