并查集知识点总结和学习心得分享

本文详细介绍了并查集的基础知识,包括无权并查集、带权并查集及其优化方法,如路径压缩和按秩合并。通过实例解析并查集在处理集合关系、计算距离和解决特定问题(如银河英雄传说、奇偶游戏等)中的应用,并探讨了并查集在超市物品销售和区间操作等拓展场景中的高效解决方案。
摘要由CSDN通过智能技术生成

先介绍一下并查集的基本功能
基础的并查集主要有两种.
第一种是普通的并查集,主要用来处理无权的相对关系,比如说A和B是一类人,B和C是一类人,那么A和C也是一类人.
第二种是带权的并查集.种类并查集也是其中的一种.具体下面会介绍.
1.无权并查集
先引入一个问题.会给N个关系.A B代表A和B是一类东西.且具有传递性.
然后给Q个询问.问A B 是否是同一类东西.如果要O(1)的询问,我们可以建立一个数组.下标代表数值,数组的值代表种类. 这种方式询问确实是O(1)的,但修改需要O(N)的.不够优秀.我们需要引入一种新的数据结构来解决这个问题.
和上面的解法类似.不过我们引入一个新的概念.让每个数值都有一个代表元.数组名字记为fa.
一开始全部数值的代表元都是它自己.之后如果A B 是同类人,就让fa[A]=fa[B]这里的fa不是指数组,而是指他的真正代表元.因为在多次合并之后fa[A]不一定是A的代表元了.可能是fa[fa[A]].这是一个套娃的问题.所以.递归求解.
如果看文字看不懂,就画图理解,无论是数据结构还是算法.画图都是一个很直观的理解方法.在这里插入图片描述
在这里.顶部的就是下面几个节点的代表元.要找到真正的代表元.可以用一个简单的get函数来表示.return x == fa[x]?x:get(fa[x]); 这个写法不够优秀.凡是树的问题就要考虑退化问题.如果退化成为一条链的时候.查找的效率还是O(n)的.所以要压缩路径.我们可以在一次递归后直接把fa[x]指向它的跟节点.
非递归版本可以更好的理解它.(效率也更高).

int get(int x){
   
	int k,j,r;
	r = x;
	while(r != fa[r]) r = fa[r];
	k = x;
	while(k != r){
   
		j = fa[k];
		fa[k] = r;
		k = j;
	}
	return r;
}

A B合并就很简单了.直接上代码了.(其实是有按秩优化的方法,但是其实影响不是很大.压缩路径之后效率均摊下来是够用的)

int unite(int x,int y){
   
	x = get(x), y = get(y);
	fa[x] = y;
}

一道很基础的例题(其实不是特别特别基础.建议配合离散化食用.)
补充一下离散化的知识点吧.所谓离散化,就是忽略具体数值,只记录相对的大小.比如说100 200 300 400 500 离散化之后可以是1 2 3 4 5.这个不会改变他本身在数组中的位置,但是可以缩小它本身的值.可以理解为给原先数组中的每一个元素都取了一个新名字,这个新名字就是离散化数组里面该元素对应的下标.
前面提到了并查集fa数组的下标是它的数值.在这题里数值是有1≤i,j≤1000000000的.太大了.数组开不下
但是1≤n≤1000000也就是说就算所有的i j都不同.最多也才2000000个数字.离散化之后下标就是0-1999999.这样子数组就开的下了.而离散化也是有模板的.
我一般是用vector存原先的数值,然后排序去重拿到处理好的数组后用lower_bound来得到新数组里面的值.
ps:这几个函数不熟悉的话建议百度一下.
离散化模板代码:

void LS(vector<int> &x){
   //x里面储存着原先所有的信息
	sort(x.begin(),x.end());
	x.erase(unique(x.begin(),x.end()),x.end());
}
int getnewpos(vector<int> x,int xx){
   
	return lower_bound(x.begin(),x.end(),xx)-x.begin();
}

本题代码


#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair<int,int>
#define pll pair<long long,long long>
#define pdd pair<double,double>
#define db double
#define fir(i,a,b) for(int i=a;i<=b;++i)
#define afir(i,a,b) for(int i=a;i>=b;--i)
#define ft first
#define vi vector<int>
#define sd second
#define ALL(a) a.begin(),a.end()
#include <bits/stdc++.h>

using namespace std;
const int N = 1e6+10;
const int mod = 9901;

LL gcd(LL a,LL b){
   return b == 0 ? a : gcd(b,a%b);}

int fa[N];
vector<int> all;
int get(int x){
   
	int k,j,r;
	r = x;
	while(r != fa[r]) r = fa[r];
	k = x;
	while(k != r){
   
		j = fa[k];
		fa[k] = r;
		k = j;
	}
	return r;
}
int getpos(int x){
   
	return lower_bound(ALL(all),x)-all.begin();
}
int main(){
   

	int t;
	cin >> t;
	while(t--){
   
		int n;
		cin >> n;
		all.clear();
		vector<pii> v1,v2;
		fir(i,1,n){
   
			int x,y,q;
			cin >> x >> y >> q;
			all.pb(x);
			all.pb(y);
			if(q) v1.pb(make_pair(x,y));
			else v2.pb(make_pair(x,y));
		}
		sort(ALL(all));
		all.erase(unique(ALL(all)),all.end());
		fir(i,0,(int)all.size()-1){
   
			fa[i] = i;
		}
		fir(i,0,(int)v1.size()-1){
   
			int x = getpos(v1[i].ft), y = getpos(v1[i].sd);
			int fx = get(x), fy = get(y);
			fa[fx] = fy;
		}
		bool f = 1;
		fir(i,0,(int)v2.size()-1){
   
			int x = getpos(v2[i].ft), y = getpos(v2[i].sd);
			int fx = get(x), fy = get(y);
			if(fx == fy){
   
				f = 0;
				break;
			}
		}
		if(f) puts("YES");
		else puts("NO");
	}
	
	return 0;
}	

2.加深对并查集的理解
引入一道例题来彻底搞懂并查集.
银河英雄传说
先考虑怎么算A B相隔了多少个飞艇.先算出A和根节点的距离d1,再算出B和跟节点的距离d2.那么abs(d1-d2)-1就是相隔的数量.我们可以用一个d[i]数组来维护这个信息.同时也要更改压缩路径的写法,只要先求出i到跟节点的d 然后每次减掉d[i]就可以了
代码

int get(int x){
   
	int k,j,r;
	r = x;
	int tmp = 0;
	while(r != fa[r]){
   
		tmp += d[r];
		r = fa[r];
	}
	k = x;
	while(k != r){
   
		j = fa[k];
		fa[k] = r;
		int cur = d[k];
		d[k] = tmp;
		tmp -= cur
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值