并查集总结

并查集简介

并查集是一种可以动态维护若干个不重叠的结合,并支持合并与查询的数据结构
并查集是一种树状的数据结构,可以用于维护传递关系以及联通性。
并查集有两种操作:

  1. find:查询一个元素属于哪个集合
  2. merge:合并两个集合

模板

  1. find函数
int find(int x)
{if(x == fa[x])	return x;reutnr fa[x] = find(fa[x]);
}
  1. merge函数
void merge(int x, int y)
{int px = find(x), py = find(y);if(px != py)	fa[px] = py;
}

模板题

村村通
对于这道题我们只需要求连通块的数量,然后将这几个联通快看成点,联通n个点需要n-1条边

#include <iostream>
#include <cstring>using namespace std;
const int N = 1010;
int p[N], st[N];int find(int x)
{if(x == p[x])   return x;return p[x] = find(p[x]);
}int main()
{// freopen("1.in", "r", stdin);while(1){memset(st, 0, sizeof st);int n, ans = 0;scanf("%d", &n);if(n == 0)  return 0;else{int m;  scanf("%d", &m);for(int i = 1; i <= n; ++ i)    p[i] = i;for(int i = 0; i < m; ++ i){int x, y;   scanf("%d%d", &x, &y);int a = find(x);int b = find(y);if(a != b)  p[a] = b;}//统计联通块的数量for(int i = 1; i <= n; ++ i){int x = find(i);if(!st[x])  ans ++, st[x] = 1;;}cout << ans - 1 << endl;}}
}

P1551 亲戚
亲戚的亲戚是亲戚,亲戚这种关系具有传递性,如果具有亲戚关系就将合并。

#include <iostream>using namespace std;const int N = 5010;int p[N];
int n, m, t;int find(int x)
{if(x != p[x])   p[x] = find(p[x]);return p[x];
}int main()
{cin >> n >> m >> t;for(int i = 1; i <= n; ++ i)    p[i] = i;for(int i = 0; i < m; ++ i){int a, b;cin >> a >> b;int fa = find(a);int fb = find(b);p[fa] = fb;}for(int i = 0; i < t; ++ i){int a, b;cin >> a>> b;int fa = find(a);int fb = find(b);if(fa != fb)    puts("No");else    puts("Yes");}return 0;
}

836. 合并集合

#include <iostream>using namespace std;const int N = 1e5+10;int n,m;
int p[N];int find(int x)
{if(p[x] != x )  p[x] = find(p[x]);return p[x];
}int main(int argc, const char * argv[]) {scanf("%d%d",&n,&m);for(int i = 0; i < n; i ++) p[i] = i;while(m--){char op[2];int a,b;scanf("%s%d%d",op,&a,&b);if(op[0] == 'M')    p[find(a)] = find(b);else{if(find(a) == find(b))  cout<<"Yes"<<endl;else puts("No");}}return 0;
}

837. 连通块中点的数量
这道题目比前面的几道模板题目需要多维护一个信息,在进行merge时我们需要将更新作为被添加枝的树的cnt
两个bug调了一个小时最后看了下评论区收获颇丰
1、合并两个集合时
如果没有按照下面的写法即省去这一步a=find(a),b=find(b);
则合并根节点的顺序与更新更新集合得顺寻不能互换,
必须要先把原来根节点中元素的数量加到所要合并的
根节点上去再把根节点合并
a=find(a),b=find(b);
cnt[b]+=cnt[a];
p[a]=b;
2、路径压缩
一定不要忘记路径压缩不然会超时!!!

#include"bits/stdc++.h"
using namespace std;
const int N=1e5+10;
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()
{cin.tie(0);cin>>n>>m;for(int i=1;i<=n;i++)   {p[i]=i;cnt[i]=1;}while(m--){string str;int a,b;cin>>str;if(str=="C"){cin>>a>>b;if(find(a)!=find(b))   {a=find(a),b=find(b);cnt[b]+=cnt[a];p[a]=b;}}else if(str=="Q1"){cin>>a>>b;if(find(a)==find(b))    cout<<"Yes"<<endl;else                    cout<<"No"<<endl;}else{cin>>a;cout<<cnt[find(a)]<<endl;}}return 0;
}

边带权并查集

推导部分和
这道题的是带边权并查集的应用,比较难想到的是建图
对于每个区间l, r,k, 我们可以由前缀和s[r] - s[l - 1] = k,我们从r连一条l-1的边
WechatIMG819.png

#include <iostream>using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
//p[N]数组用来做并查集
int p[N], n, m, q;
//s[N]数组是当前点到根结点的权值和,也是前缀和
LL s[N];//并查集的查询操作(带路径压缩)
int find(int x)
{if(x == p[x])   return x;else{int t = find(p[x]);s[x] += s[p[x]];p[x] = t;return t;}
}int main()
{// freopen("1.in", "r", stdin);cin >> n >> m >> q;for(int i = 1; i <= n; ++ i)    p[i] = i;for(int i = 0; i < m; ++ i){int l ,r;LL k; cin >> l >> r >> k;int t1 = find(l - 1), t2 = find(r);if(t1 != t2){p[t2] = t1;s[t2] = s[l - 1] - s[r] + k;}}while(q --){int l, r;cin >> l >> r;int t1 = find(l - 1), t2 = find(r);if(t1 != t2)    puts("UNKNOWN");else printf("%lld\n", s[r] - s[l - 1]);}return 0;
}

P1196 [NOI2002] 银河英雄传说
这道题目比较特殊是每个集合是一条链,一条链也是一棵树,不过是树的特殊形态,我们可以把每一列看作一个集合,用并查集去维护,另外题目中需要知道两个点之间的点有多少个,这里我们就还需要额外维护每个点到根节点路径上的权值,因为我们这里的并查集已经进行优化即使用了路径压缩,并且边权都是1,所以在维护每个点到根节点的路径上的权值时,我们还需要用到一个集合中元素的个数,也就是还需要额外维护集合中元素个数。
综上我们需要额外维护两个信息:

  1. d[x]:表示x到根节点的边权求和
  2. size[x]:表示以x为根的子树中节点数量
#include <bits/stdc++.h> 
#define ls p<<1
#define rs p<<1|1
#define PII pair<int, int>
#define ll long long
#define ull unsigned long long
#define endl '\n'
#define db double
using namespace std;const int N = 30010;
int fa[N], d[N], size[N],t;void init()
{for(int i = 1; i <= N - 1; ++ i)	fa[i] = i, size[i] = 1;
}int find(int x)
{if(x == fa[x])	return x;int root = find(fa[x]);d[x] += d[fa[x]];return fa[x] = root;
}void merge(int x, int y)
{int px = find(x), py = find(y);if(px == py)	return;fa[px] = py;d[px] = size[py];size[py] += size[px];
}void solve()
{cin >> t;init();for(int i = 1; i <= t; ++ i){char op;int x, y;cin >> op >> x >> y;if(op == 'M')	merge(x, y);else{int px = find(x), py = find(y);if(px != py)	cout << -1 << endl;else	cout << abs(d[x] - d[y]) - 1 << endl;		} }
}int main()
{ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
//	freopen("1.in", "r", stdin);
//	cin >> t;
//	while(t --)	solve(); return 0;
}

并查集的拓展域

P2024 [NOI2001] 食物链
这是一道并查集的拓展域的题目也可以用带权并查集去做
普通并查集维护的是不相交集合,是一种传递性的关系如亲戚的亲戚是亲戚
天敌的天敌是食物,是一种环形的关系
我们如果用拓展域来解决这道题目的话可以用3个并查集来维护3种关系,第一种是同类关系,第二种是食物关系,第三种是天敌
我们不用真开三个并查集,我们可以将三个并查集开到一个数组里,下标的范围代表不同的集合
WechatIMG824.jpeg

#include <iostream>using namespace std;
const int N = 5e4 + 10;
int p[N * 3], ans, k, n;//1--n代表x的同类,n + 1 -- 2n代表x的食物, 2n + 1 -- 3n代表x的天敌
int find(int x)
{if(x == p[x])   return x;return p[x] = find(p[x]);
}
void merge(int x, int y)
{int px = find(x), py = find(y);p[py] = px;
}
int main()
{cin >> n >> k;for(int i = 1; i <= 3 * n; ++ i)    p[i] = i;for(int i = 0; i < k; ++ i){int d, x, y;scanf("%d%d%d", &d, &x, &y);if(x > n || y > n)  ans ++;//x、y是同类else if(d == 1){//如果根据前面的信息,我们可以知道y在x的食物域,//或者y在x的天敌域中,说明这句话是假话if(find(x + n) == find(y) || find(x + n + n) == find(y))    ans ++;//如果根据前面的信息,不能判断这句话是错误的,那么就讲这句话//当成对的并且更新x的三个域else{//y在x的同类域中merge(x, y);//y的食物和x的食物是同类merge(x + n, y + n);//y的天敌和x的天敌是同类merge(x + n + n, y + n + n);}}//如果x吃yelse{//若果y在x的同类域或者,y在x的天敌域说明这句话是假话if(find(x) == find(y) || find(x + n + n) == find(y))    ans ++;//这句话是真话就更新x的三个域else{//y在x的食物域中merge(x + n, y);//y的食物是x的天敌merge(x + n + n, y + n);//y的天敌是x的同类merge(x, y + n + n);}}}cout << ans << endl;return 0;
}

例题团伙

#include <iostream>
using namespace std;
const int N = 2010;
int p[N];
bool st[N];
int find(int x)
{if(p[x] == x)   return p[x];return p[x] = find(p[x]);
}
void merge(int x, int y)
{int px = find(x), py = find(y);if(px != py)    p[px] = py;
}int main()
{// freopen("1.in", "r", stdin);int n, m;scanf("%d%d", &n, &m);for(int i = 1; i <= 2 * n; ++ i)    p[i] = i;for(int i = 0; i < m; ++ i){char op; int x, y;cin >> op >> x >> y;if(op == 'F'){merge(x, y);// merge(x + n, y + n);}else{merge(x + n, y);merge(x, y + n);}}// for(int i = 1; i <= n; ++ i)    cout << i << ' ' << find(i) << endl;    int cnt = 0;for(int i = 1; i <= n; ++ i)if(!st[find(i)]){cnt ++;st[find(i)] = 1;}cout << cnt << endl;return 0;
}

P5937 [CEOI1999] Parity Game

题目的具体思路如下:考虑一段连续区间的1个数的奇偶,可以转化为考虑考虑两个端点前缀和的奇偶性上,分为两种情况,如果[l, r]区间内1的个数为偶数,说明sum[r]和sum[l - 1]包含1的个数的奇偶性相同,反之若为奇数则两个包含1的个数的奇偶性相反,我们知道奇偶性具有传递性,这样我们就可以种类并查集来维护,注意n的范围比较大,但是实际的需要用到的点的个数是比较小的,这里我们需要离散化一下。

#include <bits/stdc++.h> 
#define LL long long 
using namespace std;const int N = 1e4 + 10;
int p[N * 2 + 10], n, m, k;
map<int, int>mp;
int b[N * 2];struct wy
{int l, r, op;	
}q[N];int find(int x)
{if(x != p[x])	p[x] = find(p[x]);return p[x];
}void merge(int x, int y)
{int px = find(x), py = find(y);p[py] = px;
}int query(int x)
{return lower_bound(b + 1, b + 1 + k, x) - b;
}
void solve()
{scanf("%d%d", &n, &m);for(int i = 1; i <= m; ++ i){int l, r, op;char s[5];scanf("%d%d%s", &l, &r, s);if(s[0] == 'e')	op = 1;else op	= 2;q[i] = {--l, r, op};b[++ k] = l;b[++ k] = r;} sort(b + 1, b + 1 + k);k = unique(b + 1, b + 1 + k) - (b + 1);for(int i = 1; i <= 2 * k; ++ i) p[i] = i;for(int i = 1; i <= m; ++ i){int l = query(q[i].l), r =  query(q[i].r), op = q[i].op;if(op == 1) {if(find(l) == find(r + k) || find(r) == find(l + k)){printf("%d", i - 1);return;}else{merge(l, r);merge(l + k, r + k);}}else{if(find(l) == find(r) || find(r + k) == find(l + k)){printf("%d", i - 1);return;}else{merge(l, r + k);merge(r, l + k); 	}}	}printf("%d", m);
}
int main()
{
//	freopen("1.in", "r", stdin);solve(); return 0;
}

应用

P1955 [NOI2015] 程序自动分析

这道题目是相等关系,相等关系也具有传递性,明显我们可以用并查集来维护。
我们可以先对处理相等,然后去查询不相等的是否在一个集合里面如果在一个集合里面则说明这样的点是不存在的。这道题目的数据的范围很大,但实际用到的很少,我们需要对数据进行离散化。

#include <bits/stdc++.h> 
#define LL long long 
using namespace std;const int N = 1e6 + 10;
int n, m, p[N], a[N], k, tot;
struct wy{int x, y, e;
}q[N];int find(int x)
{if(x != p[x])	p[x] = find(p[x]);return p[x];
} void merge(int x, int y)
{int px = find(x), py = find(y);p[py] = px;
}void solve()
{scanf("%d", &n);for(int i = 1; i <= n; ++ i){int x, y, e;scanf("%d%d%d", &x, &y, &e);a[++ tot] = q[i].x = x;a[++ tot] = q[i].y = y;q[i].e = e;	}sort(a + 1, a + 1 + tot);tot = unique(a + 1, a + 1 + tot) - a - 1;for(int i = 1; i <= tot; ++ i)	p[i] = i;for(int i = 1; i <= n; ++ i){q[i].x = lower_bound(a + 1, a + tot + 1, q[i].x) - a - 1;q[i].y = lower_bound(a + 1, a + tot + 1, q[i].y) - a - 1;if(q[i].e == 1){merge(q[i].x, q[i].y);}}for(int i = 1; i <= n; ++ i){int x = q[i].x;int y = q[i].y;if(q[i].e == 0 && find(x) == find(y)){puts("NO");return;}}puts("YES");
}int main()
{
// 	freopen("1.in", "r", stdin);scanf("%d", &k);while(k--) solve(); return 0;
}

P1525 [NOIP2010 提高组] 关押罪犯
贪心+并查集,我们优先选择边权最大的罪犯,首先查询他们是否已经在一个集合 如果不在,分别将他们放进不同监狱(集合),如果在则说明我们已经找到了答案

#include <bits/stdc++.h> 
#define ls p<<1
#define rs p<<1|1
#define PII pair<int, int>
#define ll long long
#define ull unsigned long long
#define endl '\n'
#define db double
using namespace std;const int N = 1e6 + 10, e = 20010;struct wy
{int x, y, val;bool operator < (const wy & t) const{return t.val < val;}
}a[N];int p[N + 1];
int n, m;void init()
{for(int i = 1; i <= N; ++ i)	p[i] = i;
}int find(int x)
{if(x == p[x])	return x;return p[x] = find(p[x]);	
}void merge(int x, int y)
{int px = find(x), py = find(y);if(px != py)	p[py] = px;
}void solve()
{cin >> n >> m;for(int i = 1; i <= m; ++ i){int x, y, val; cin >> x >> y >> val;a[i] = {x, y, val};}sort(a + 1, a + 1 + m);init();for(int i = 1; i <= m; ++ i){int x = a[i].x, y = a[i].y, val = a[i].val;int px = find(x), py = find(y);if(px == py)	{cout << val << endl;return;}else{merge(y + n, x);merge(x + n, y);}}cout << 0 << endl;
}
int main()
{ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
//	freopen("1.in", "r", stdin);
//	cin >> t;
//	while(t --)	solve(); return 0;
}

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

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

相关文章

爆款文章有诀窍,内容创作者如何能持续产出优质内容

内容营销人有没有这么一种共鸣&#xff1a;10 万 那么多&#xff0c;为什么不能多我一个&#xff1f; 通常&#xff0c;我们把浏览量 / 阅读量高、转评赞数量高的内容看作爆款&#xff0c;而数据如果达到 10 万 则是超级爆款。因为&#xff0c;阅读量高意味着内容得到了大量的曝…

8年老鸟整理,自动化测试-准备测试数据详细...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 大部分类型的测试…

基于C#实现Bitmap算法

在所有具有性能优化的数据结构中&#xff0c;我想大家使用最多的就是 hash 表&#xff0c;是的&#xff0c;在具有定位查找上具有 O(1)的常量时间&#xff0c;多么的简洁优美&#xff0c;但是在特定的场合下&#xff1a; ①&#xff1a;对 10 亿个不重复的整数进行排序。 ②&am…

AI原生应用为百度带来新增量

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; AI将彻底改变每一个行业!得益于AI和基础模型的驱动&#xff0c;百度在AI原生应用领域厚积薄发。 11月21日&#xff0c;百度Q3财报发布&#xff0c;数据显示&#xff1a;三季度营收达344.47亿元&…

Redis篇---第九篇

系列文章目录 文章目录 系列文章目录前言一、如果有大量的 key 需要设置同一时间过期,一般需要注意什么?二、什么情况下可能会导致 Redis 阻塞?三、缓存和数据库谁先更新呢?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击…

Axios简单使用与配置安装-Vue

安装Axios npm i axios main.js 导入 import Axios from axios Vue.prototype.$axios Axios简单发送请求 get getTest() {this.$axios({method: GET,url: https://apis.jxcxin.cn/api/title?urlhttps://apis.jxcxin.cn/}).then(res > {//请求成功回调console.log(res)}…

②⑩ 【MySQL Log】详解MySQL日志:错误日志、二进制日志、查询日志、慢查询日志

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ MySQL日志 ②⑩ MySQL日志&#xff1a;错误日志…

SpringBoot3.x最简集成SpringDoc-OpenApi

为什么使用SpringDoc 在SpringBoot低版本时一般使用Swagger扫描接口生成Json格式的在线文档&#xff0c;然后通过swagger-ui将Json格式的文档以页面形式展示文档。可惜遗憾的是swagger更新到3.0.0版本(springfox)后不更新了。 SpringBoot3.x以后需要的JDK版本最低为Java17&…

MQ和redis的内部原理一些总结

首先&#xff0c;先知道内部原理&#xff1b;其次&#xff0c;就是查官方文档实战了。 但是如果不熟悉内部原理&#xff0c;那么仅仅只是安装官方文档&#xff0c;并不能排除跟踪问题和故障、预防风险等策略&#xff1b; 以下总结图解&#xff1a;&#xff08;mysql 8.0新增的…

YOLO目标检测——卫星遥感舰船检测数据集下载分享【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;卫星遥感舰船检测数据集说明&#xff1a;卫星遥感舰船检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;含船一个类别标签说明&#xff1a;使用lableimg标注软件标注&#xff0c;标注框质量高&#xff0c;含voc(xm…

Redis的持久化

redis是一个内存数据库,是把数据存储在内存中的,而我们知道内存中的数据是不持久的,一旦服务器重启或者进程重启,内存的数据就丢失了.为了让数据达到持久化的效果,就必须把数据写到硬盘上. redis相对于mysql这样的关系型数据库最明显的优势就是快.所以为了保证速度快,数据还得…

动态跳过测试用例

动态跳过测试用例 说明 我们可以通过指定环境变量来动态判断是否执行指定的测试用例设置环境变量有很多种方法&#xff0c;例如命令行方式&#xff0c;格式&#xff1a;--env keyval1,key2val2 &#xff0c;若需要指定多个环境变量则需要逗号来隔开&#xff0c;而不是空格 t…

Live800:企业提升客户互动体验,有哪些关键因素?

如今&#xff0c;随着信息时代的不断发展&#xff0c;企业已经不再是单向的商业机构&#xff0c;他们需要与客户进行及时的沟通与反馈&#xff0c;从而更好地提升客户互动体验&#xff0c;达到营销和用户体验的双赢局面。那么&#xff0c;企业如何提升客户互动体验呢&#xff1…

设计模式——RBAC 模型详解

1.什么是 RBAC 呢&#xff1f; RBAC 即基于角色的权限访问控制&#xff08;Role-Based Access Control&#xff09;。这是一种通过角色关联权限&#xff0c;角色同时又关联用户的授权方式。 简单地说&#xff1a;一个用户可以拥有若干角色&#xff0c;每一个角色又可以被分配…

Linux程序之可变参数选项那些事!

一、linux应用程序如何接收参数&#xff1f; 1. argc、argv Linux应用程序执行时&#xff0c;我们往往通过命令行带入参数给程序&#xff0c;比如 ls /dev/ -l 其中参数 /dev/ 、-l都是作为参数传递给命令 ls 应用程序又是如何接收这些参数的&#xff1f; 通常应用程序都…

Raspberry Pi 5 新一代单板计算机:树莓派5代 (介绍、入门、解疑)

树莓派5代正式发布后&#xff0c;硬件和性能的全面升级让众多开发者们都想入手感受一波&#xff0c;外观上Raspberry Pi 5 与前代产品非常相似&#xff0c;不过&#xff0c;在保留信用卡大小的整体尺寸的同时&#xff0c;也更新了一些设计元素&#xff0c;以适应新芯片组的功能…

python实现调和反距离空间插值法AIDW

1 简介 AIDW 主要是针对 IDW 的缺点进行了改进&#xff0c;考虑了样本点与预测点的位置&#xff0c;即方向和距离&#xff0c;具体见下图&#xff1a; 2 改进 IDW 公式&#xff1a; 从IDW算法可看出&#xff0c;插值点的估算值仅与插值样本距插值点的远近相关&#xff0c;并未…

贝叶斯AB测试

AB测试是用来评估变更效果的有效方法&#xff0c;但很多时候会运行大量AB测试&#xff0c;如果能够在测试中复用之前测试的结果&#xff0c;将有效提升AB测试的效率和有效性。原文: Bayesian AB Testing[1] 随机实验&#xff0c;又称AB测试&#xff0c;是行业中评估因果效应的既…

【Mysql】[Err] 1293 - Incorrect table definition;

基本情况 SQL文件描述 /* Navicat MySQL Data TransferSource Server : cm4生产-200 Source Server Version : 50725 Source Host : 192.168.1.200:3306 Source Database : db_wmsTarget Server Type : MYSQL Target Server Version : 50725 File…

vxe编辑保存表格

业务需求&#xff1a; 1、需要点击编辑时&#xff0c;全部表格显示编辑框&#xff0c;点击保存&#xff0c;全部保存。 2、因为位置问题&#xff0c;产品经理把24小时分成了两行&#xff0c;开发就得分两个表格。列标题是写死的&#xff0c;文字偏移也是写死的&#xff0c;其他…