并查集 板子

博客介绍了并查集相关知识,加上路径压缩后,并、查时间复杂度近似O(1)。阐述了并查集核心,包括初始化、find函数及集合操作。还提到记录集合元素数的方法,以食物链问题为例,说明需维护节点到根的距离,通过模三判断关系及距离维护方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

加上路径压缩 并、查的时间复杂度都近似O(1)

在这里插入图片描述
在这里插入图片描述

并查集核心:1. 初始化 a[x]=x所有元素各自是一个集合 2. find(路径压缩优化)的函数 3. 合并集合、查询是否属于同一集合的操作

#include<bits/stdc++.h>
using namespace std;

const int N=100010;
int a[N];

int find(int x){
	if(a[x]!=x) a[x]=find(a[x]);
	return a[x];
}

int main(){
	int n,m; cin >>n>>m;
	for(int i=1;i<=n;i++){
		a[i]=i;
	}
	
	string op;
	while(m--){
		cin>>op;
		if(op=="M"){// 合并 
			int x,y;
			cin>>x>>y;
			a[find(x)]=find(y);
		}
		if(op=="Q"){// 查是否属于同一集合 
			int x,y;
			cin>>x>>y;
			if(find(x)==find(y)) cout<<"Yes"<<endl;
			else cout<<"No"<<endl;
		}
	}
}

加上记录集合元素数 则用size[N] 在合并时更新size

#include<bits/stdc++.h>
using namespace std;

const int N=100010;
int a[N],size[N];                 加size数组计数

int find(int x){
	if(a[x]!=x) a[x]=find(a[x]);
	return a[x];
}

int main(){
	int n,m; cin >>n>>m;
	for(int i=1;i<=n;i++){
		a[i]=i;
		size[i]=1;                 初始每个集合大小为1
	}
	
	string op;
	while(m--){
		cin>>op;
		if(op=="M"){// 合并 
			int x,y;
			cin>>x>>y;
			if(find(x)==find(y)) continue;  已属于一个集合 跳过
			size[find(y)]+=size[find(x)];   size更新
			a[find(x)]=find(y);
		}
		if(op=="Q"){// 查是否属于同一集合 
			int x,y;
			cin>>x>>y;
			if(find(x)==find(y)) cout<<"Yes"<<endl;
			else cout<<"No"<<endl;
		}
	}
}

eg 食物链 需要维护每个节点到根的距离

在这里插入图片描述

根据两个点到根的距离之差模三 来考虑这两点的关系(同类 吃 被吃)

这个距离并非真实距离 而是只考虑其“模3性”

具体如何维护距离:find中距离累加 + 合并操作中被合并的根节点要加新距离

#include<bits/stdc++.h>
using namespace std;

const int N=50000;
int a[N],d[N];   // d[N]维护到根节点距离 

int find(int x){
	if(a[x]!=x) {
		int t=find(a[x]);// t先存着根 
		d[x]+=d[a[x]];    //求距离 
		a[x]=t;  //最后再压缩路径 
	}
	return a[x];
}

int main(){
	int f=0;
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		a[i]=i;
	}
	while(m--){
		int op,x,y;
		cin>>op>>x>>y;
		int px=find(x);// x的根 
		int py=find(y);// y的根 
		if(x>n||y>n){     // 大于N 错 
			f++;
			continue;
		}
		if(op==2){
			if(x==y)  f++;// 自己吃自己 错 
				
			else{
				//x y都在树中 那么可以知道其关系了 若x吃y 则 (d[x]-d[y]-1)%3=0(距离模3余1)
				//若z吃x 按这种方式构造 z对y距离模3余2 
				//若m吃z 按这种方式构造 m对y模3余0  同类 
				if(px==py&&(d[x]-d[y]-1)%3) f++; 
				
				else if(px!=py){// x y不在一棵树 这句话就看成正确 那么要合并+构造符合要求的距离 
					a[px]=py; //合并 
					d[px]=d[y]-d[x]+1;//应有:(d[x]+d[px]-d[y]-1)%3=0  可推出d[px] 
				}
			}
		}
		if(op==1){
			if(px==py&&(d[x]-d[y])%3) f++;
			
			else if(px!=py){
				a[px]=py;	//合并 
				d[px]=d[y]-d[x];//应有:(d[x]+d[px]-d[y])%3=0  可推出d[px] 
			}
		}
	}
	cout<<f;
}

综上

(1)朴素并查集:

    int p[N]; //存储每个点的祖宗节点

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ ) p[i] = i;

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);


(2)维护size的并查集:

    int p[N], size[N];
    //p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        size[i] = 1;
    }

    // 合并a和b所在的两个集合:
    size[find(b)] += size[find(a)];
    p[find(a)] = find(b);


(3)维护到祖宗节点距离的并查集:

    int p[N], d[N];
    //p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x)
        {
            int u = find(p[x]);
            d[x] += d[p[x]];
            p[x] = u;
        }
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        d[i] = 0;
    }

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);
    d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值