算法------(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.hqwc.cn/news/461909.html

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

相关文章

【Spring源码解读!底层原理高级进阶】【上】探寻Spring内部:BeanFactory和ApplicationContext实现原理揭秘✨

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《Spring 狂野之旅&#xff1a;底层原理高级进阶》 &#x1f680…

【leetcode热题100】分隔链表

给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。 示例 1&#xff1a; 输入&#xff1a;head [1,4,3,2,5,2], x 3 输出&am…

Maven 安装教程

一、安装地址 1.官网安装最新版本 2.其他版本&#xff0c;我这里是maven-3/3.6.2 二、配置环境 1. 点击此电脑鼠标右击->属性->高级系统设置->环境变量 &#xff0c;配置系统变量->新建&#xff1a;MAVEN_HOME 2.配置path 路径 &#xff1a;%MAVEN_HOME%\bin 三、安…

SpringMVC速成(一)

文章目录 SpringMVC速成&#xff08;一&#xff09;1.SpringMVC概述2.SpringMVC入门案例2.1 需求分析2.2 案例制作步骤1:创建Maven项目步骤2:补全目录结构步骤3:导入jar包步骤4:创建配置类步骤5:创建Controller类步骤6:使用配置类替换web.xml步骤7:配置Tomcat环境步骤8:启动运行…

【算法】一文带你快速入门动态规划算法以及动规中的空间优化

君兮_的个人主页 即使走的再远&#xff0c;也勿忘启程时的初心 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;如果给算法的难度和复杂度排一个排名&#xff0c;那么动态规划算法一定名列前茅。在最开始没有什么整体的方法的时候&#xff0c;我也曾经被动态…

MySQL 图书管理系统

1.需求分析 1.1项目需求分析简介 1.1.1信息需求分析 (1) 图书信息&#xff1a;包括书籍编号&#xff0c;书籍名称&#xff0c;出版社&#xff0c;作者&#xff0c;库存量&#xff0c;出版日期&#xff0c;价格&#xff0c;库存&#xff0c;剩余量&#xff0c;类别等&#xf…

上下固定中间自适应布局

实现上下固定中间自适应布局 1.通过position&#xff1a;absolute实现 定义如下结构 <body> <div class"container"> <div class"top"></div> <div class"center"></div> <div class"bottom&…

清平乐-春风丽日

今天&#xff0c;是2024年农历除夕日&#xff0c;远方家人已于昨夜风尘扑扑地倦鸟归巢&#xff0c;团聚过龙年&#xff0c;今晨酣睡未起。老龄笔者心情极佳&#xff0c;一夜好梦醒来&#xff0c;推窗仰头展望苍穹&#xff0c;喜上心头&#xff1a;啊&#xff01;接连几天的小雨…

小区创业项目推荐:小投资大回报的店铺类型

作为一位拥有5年鲜奶吧创业经验的自媒体博主&#xff0c;我深知在小区内寻找一个既小投资又能带来大回报的创业项目是多么重要。今天&#xff0c;我要为大家推荐的&#xff0c;正是这样一个项目——鲜奶吧。 一、鲜奶吧&#xff1a;小区内的健康食品新宠 随着健康饮食观念的深…

STL之list容器的介绍与模拟实现+适配器

STL之list容器的介绍与模拟实现适配器 1. list的介绍2. list容器的使用2.1 list的定义2.2 list iterator的使用2.3 list capacity2.4 list element access2.5 list modifiers2.6 list的迭代器失效 3. list的模拟实现3.1 架构搭建3.2 迭代器3.2.1 正向迭代器3.2.2反向迭代器适配…

【doghead】uv_loop_t的创建及线程执行

worker测试程序,类似mediasoup对uv的使用,是one loop per thread 。创建一个UVLoop 就可以创建一个uv_loop_t Transport 创建一个: 试验配置创建一个: UvLoop 封装了libuv的uv_loop_t ,作为共享指针提供 对uv_loop_t 创建并初始化

如何运行心理学知识(心流)来指导工作和生活

如何运用心流来指导工作和生活 如何联系我 作者&#xff1a;鲁伟林 邮箱&#xff1a;thinking_fioa163.com或vlinyes163.com GitHub&#xff1a;https://github.com/thinkingfioa/ReadingSummary 版权声明&#xff1a;文章和记录为个人所有&#xff0c;如果转载或个人学习…