亲戚(并查集)

题目背景

若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

题目描述

规定:x和y是亲戚,y和z是亲戚,那么x和z也是亲戚。如果x,y是亲戚,那么x的亲戚都是y的亲戚,y的亲戚也都是x的亲戚。

输入格式

第一行:三个整数n,m,p,(n<=5000,m<=5000,p<=5000),分别表示有n个人,m个亲戚关系,询问p对亲戚关系。
以下m行:每行两个数Mi,Mj,1<=Mi,Mj<=N,表示Mi和Mj具有亲戚关系。
接下来p行:每行两个数Pi,Pj,询问Pi和Pj是否具有亲戚关系。

输出格式

P行,每行一个’Yes’或’No’。表示第i个询问的答案为“具有”或“不具有”亲戚关系。

样例:

6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6

Yes
Yes
No

根据题目的意思,因为是一个“亲戚关系图”,因此可以先创造一个图G(V, E),因此n, m就分别是指G的n个点,m条边,这里可以采用样例的6,5,然后再根据样例中给出的“亲戚关系”去在点之间建边,于是就得到了下面这个图:
在这里插入图片描述
有点小没办法qwq

很显然我们可以看出,这是一个无向图,而且很显然这个并不是一个连通图(当然也有是连通图的时候,那种特殊情况待会讨论)
那么显而易见,现在除了6之外的所有点都是亲戚,也就是说无6的那个子图是一个无向联通子图,所以说可以得出结论,在任意联通子图中的两个点必为亲戚关系,为亲戚关系的两个点在同一联通子图中
所以根据这个性质,w们就把问题转化为:在一个无向图中,查询两点是否在同一联通子图中
那么很显然,我们肯定是开一个vis数组,用邻接表存,然后走到一个点就更新vis,如果无路可走那么就去遍历下一个联通子图
这种方法的话存在两种非常极限的情况,第一种是在G中根本没有边相连,则需要遍历的次数就退化成了O(n),也就是每一个点都需要遍历一次
第二种极限的情况就是当所有点都相连的时候,遍历查找整个邻接表的复杂度就退化为O(m)甚至更高

很显然这种方法面对小数据是能够过的 (其实这道题也能这么水),不过很显然如果遇到了大数据还没n^2就过百万了。。。

众所周知,在利用邻接表存储的时候,w们是用了一个类似hash表的样子的一个东西对于图进行存储的一个东西,所以在w们存储的时候本质就是给它存成了一堆链表,而链表的查询复杂度在特殊情况其实是比较高的,再加上可能会出现毒瘤数据,所以有的时候甚至会卡出接近O(nm)的复杂度

但是,请思考一个问题:

我们真的需要去遍历每个子图嘛?

很显然并不是,因为w们只需要知道需要查询的元素和已知与需要查询的元素有关联的一个点与另外一个需要查询的元素是否有关系即可,而对于这种关系,我们可以在处理的时候就让一些点同时处于一个集合中,而在该集合要在集合中任取一个元素作为该集合的代表元素,然后可能会出现一个集合中有一个元素是另一个集合的代表元素的情况,很显然这就让我们想起了另外一个数据结构:

那么每一个联通子图就变成了一个个的树,而整个图就变成了一个森林
那么我们有必要按照树的存储方式来存图嘛?很显然我们并不需要,因为我们其实只是对于一个集合进行了抽象而已,也就是说我们只需要存下标为i的点所在集合的代表元素即可

这就是基础的并查集

可是这仅仅是基础,因为w们还有可能会遇到一些更加极限的情况

如:
在这里插入图片描述
在这种连成一条线的情况下
时间复杂度又退化成了O(n)

在这里,我们就可以用到

路径压缩 按秩合并

大法了

下面附上代码
#include<bits/stdc++.h>
using namespace std;
int n,m,p;
int rank[5005];
int fa[5005];
void init(){
	for(int i=1;i<=n;i++)
		fa[i]=i,rank[i]=0;
}
int find(int x){	//路径压缩
	if(fa[x]!=x)
		fa[x]=find(fa[x]);
	return fa[x];
}
void Union(int x,int y){	//按秩合并
	x=find(x);
	y=find(y);
	if(rank[x]>rank[y])
        fa[y]=x; 
    else if(rank[x]<rank[y])
            fa[x]=y;
         else{
            fa[x]=y;
            rank[y]++;
         }
}
int main(){
	scanf("%d%d%d",&n,&m,&p);
	init();
	while(m--){
		int pi,pj;
		scanf("%d%d",&pi,&pj);
		Union(pi,pj);
	}
	while(p--){
		int pi,pj;
		scanf("%d%d",&pi,&pj);
		if(find(pi)==find(pj))
			printf("Yes\n");
		else
			printf("No\n");
	}
	return 0;
}

完美AC

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值