亲戚 - 并查集的使用

260 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

一、题目描述:

给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。x 和 y 是亲戚,y 和 z 是亲戚,那么 x 和 z 也是亲戚。如果 x,y 是亲戚,那么 x 的亲戚都是 y 的亲戚,y 的亲戚也都是 x 的亲戚。

来源:洛谷 www.luogu.com.cn/problem/P15…

输入格式

第一行:三个整数 n,m,p(n,m,p≤5000),分别表示有 n 个人,m 个亲戚关系,询问 p 对亲戚关系。

以下 m 行:每行两个数 Mi,Mj, 1≤Mi, Mj≤N,表示 Mi和Mj 具有亲戚关系。

接下来 p 行:每行两个数 Pi, Pj,询问  Pi 和 Pj 是否具有亲戚关系。

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

输出格式

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

Yes
Yes
No

二、思路分析:

并查集即合并查询。我们这题的思路给每个人做一个标记,就是它的“祖先” ,如果这两个人的祖先是同一个人的话,那么它们就有亲戚关系。

首先n个人,每个人都是自己的祖先。 当x和y有亲戚关系,那就把y的祖先设置为x的祖先或者把x的祖先设置为y的祖先, 并且,也是最重要的部分利用递归在查找x或者y的祖先的时候,把它们的祖先都统一,终止条件就是当z的祖先是z自己的话,那它就是那一串的祖先,然后把z返回给上一层并赋值(这里赋值就是统一祖先)。

image.png 这就是上面那个例子的运算过程, 橙色的就是每次输入一组亲戚关系会关联到的地方(用的表格展示,动画太难了,我不会....)

三、AC 代码:


import java.util.Scanner;
public class Main {
     // f 就是亲戚关系的数组
	static int [] f ;
	public static void main(String[] args) {
		//有n个人,m个亲戚关系,询问p对亲戚关系。
		Scanner sr = new Scanner(System.in);
		int n = sr.nextInt() ;
		int m = sr.nextInt() ;
		int p = sr.nextInt() ;
		f = new int [n+1];
           // 赋初值,也就是上面所说的 每个人都是自己的祖先
		for( int i = 1 ; i <= n ; i++ )
			f[i] = i ;
		for( int i = 0 ; i < m ; i++ ) {
		        // Mi和Mj具有亲戚关系
			int Mi = sr.nextInt() ;
			int Mj = sr.nextInt() ;
			int xx = fun( Mi ) ; // 找Mi的祖宗
			int yy = fun( Mj ) ; // 找Mj的祖宗
			f[yy] = xx ;         // 把xx和yy的祖宗统一	
		}
           // Pi和Pj是否具有亲戚关系。
		for( int i = 0 ; i < p ; i++ ) {
			int Pi = sr.nextInt() ;
			int Pj = sr.nextInt() ;
                // 判断祖宗是否一样
			if( fun(Pi) == fun(Pj) ) {
				System.out.println("Yes");
			}else {
				System.out.println("No");
			}
		}
	}
     // 使用递归查询祖宗,统一祖宗
	private static int fun(int x) {
           //  递归的终止条件 - 找到了祖宗
		if( f[x] == x )
			return x ;
           //  把x的祖宗赋值给f[x]
		return f[x] = fun( f[x] ) ;
	}

}

四、总结:

总的来说就是把两个有亲戚关系的人放到一个集合里面,这个集合的标记就是 “祖先”, 把输入数据分到不同或者相同的集合里面,重点在这个统一祖宗函数(fun)。完了就可以直接判断了