亲戚关系问题
题目背景
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
题目描述
规定:x 和 y 是亲戚,y 和 z 是亲戚,那么 x 和 z 也是亲戚。如果 x,y 是亲戚,那么 x 的亲戚都是 y 的亲戚,y 的亲戚也都是 x 的亲戚。
输入格式
第一行:三个整数 n,m,p,(n,m,p≤5000),分别表示有 n 个人,m 个亲戚关系,询问 p 对亲戚关系。
以下 m 行:每行两个数 Mi,Mj,1≤Mi,Mj≤n,表示 Mi 和 Mj 具有亲戚关系。
接下来 p 行:每行两个数 Pi,Pj,询问 Pi 和 Pj 是否具有亲戚关系。
输出格式
p 行,每行一个 Yes 或 No。表示第 i 个询问的答案为'具有'或'不具有'亲戚关系。
输入输出样例
输入 #1
6 5 3 1 2 1 5 3 4 5 2 1 3 1 4 2 3 5 6
输出 #1
Yes Yes No
并查集
上面的题就是并查集的板子。我们仍然先抛开它不谈,讲讲并查集。
并查集,顾名思义,'并'就是合并,'查'就是查询。首先先讲定义。
定义
在下文中,我们定义:
fa[i] 为 i 的父亲。
find(i) 的值是 i 的祖先(不只是它的父亲,是它父亲的父亲……也就是根)。
思路
如何判断 i 和 j 是否是亲戚?根据上文的定义,我们知道 find(i) 是 i 的祖先。那么判断 i 和 j 是否是亲戚,只需要看他们的祖先是否是一个人就行了。生活常识,如果两个人的祖先一样,那他们就是亲戚。
既然关键在这里,那么 find 函数又该怎么实现呢?接下来,我们讲并查集的'并'。
'并'
用 find 函数来实现'并',首先它的参数为 x,表示需要返回 x 的祖先。
如何找到一个节点的祖先?是的,找到它的父亲,然后是父亲的父亲……直到找到某位父亲,其没有父亲即可。这个节点就是祖先。
这个反复往上'找父亲'的过程,很容易让人想到递归。
x 的父亲是 fa[x],fa[x] 的父亲是 fa[fa[x]]……如何判断一个节点没有父亲呢?初始化时,我们将所有节点的父亲都设置成它自己。(在输入的时候给它真正的父亲,若没有,则代表它是一个祖先)
for(int i=1;i<=n;i++) fa[i]=i;
所以如果它的 fa(父亲)是它自己,那么它就是祖先。我们找到了递归的出口。
反之,如果它有父亲呢?
往上跳(递归),直到跳到出口(祖先)。
所以 find 函数(找祖先)的代码:
int find(int x) { if(fa[x]==x) return x;//祖先的编号(自己) else return fa[x];//往上跳 }
路径压缩
想一下,每一次查询,都要往上跳一遍,非常浪费时间。而且'并'的过程似乎没有体现出来。


