并查集
引例:
P 1551 \mathtt{P1551} P1551 加强版( r e l a t i v e \mathtt{relative} relative)
题目背景:
若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。
题目描述:
规定: x x x和 y y y是亲戚, y y y和 z z z是亲戚,那么 x x x和 z z z也是亲戚。如果 x x x, y y y是亲戚,那么 x x x的亲戚都是 y y y的亲戚, y y y的亲戚也都是 x x x的亲戚。
输入格式:
第一行:三个整数 n , m , p n,m,p n,m,p,( n , m , p ≤ 100000 n,m,p \leq 100000 n,m,p≤100000),分别表示有 n n n个人, m m m个亲戚关系,询问 p p p对亲戚关系。
以下 m m m行:每行两个数 M i , M j M_i,M_j Mi,Mj, 1 ≤ M i , M j ≤ N 1 \leq M_i,M_j \leq N 1≤Mi,Mj≤N,表示 M i M_i Mi和 M j M_j Mj具有亲戚关系。
接下来 p p p行,每行两个数 P i , P j P_i,P_j Pi,Pj,询问 P i P_i Pi和 P j P_j Pj是否具有亲戚关系。
输出格式:
p p p行,每行一个 Y e s \mathtt{Yes} Yes或 N o \mathtt{No} No。表示第 i i i个询问的答案为“具有”或“不具有”亲戚关系。
这个题目,如果利用简单数组存储信息(即是否为亲戚关系,是的数据相同,不是的数据不同),那么就需要 O ( n 2 ) O(n^2) O(n2)的时间维护这个“数组”,但数据规模达到了 100000 100000 100000,肯定超时。
虽然这样会超时,但是这样的思想却是正确的(即“并查集”)。并查集提供的操作有:
- 合并两个集合。(并)
- 查看两个元素是否属于同一个集合。(查)
刚才的解法,正式并查集的一种形式。但这样做会超时。所以我们需要一种更高效的并查集实现形式。这种形式就是树形。
那么,我们该如何实现树形的并查集呢?
- 合并:将某一集合的树根指向另一集合。
- 查找:一步一步找到自己所在树的根节点。
我们可以用一个 f a t h e r \mathtt{father} father数组来记录自己的父亲,那么合并( m e r g e \mathtt{merge} merge)和查找( f i n d \mathtt{find} find)操作分别如下:
int find(int x)
{
if(father[x]!=x)return find(father[x]);
return x;
}
void merge(int x,int y)
{
int fx=find(x);
int fy=find(y);
if(fx!=fy)father[fx]=fy;
}
其中,如果某个节点
x
\mathtt{x}
x是根节点,那么father[x]=x
。因此,并查集初始化(
i
n
i
t
\mathtt{init}
init)时,是这样的:
void init(int x)
{
for(int i=1;i<=x;i++)
father[i]=i;
}
然而,
为什么会 T L E \mathtt{TLE} TLE呢??
因为:如果一颗树是一条链,这样查找时肯定会超时。
解决方法:查找时直接把遇到的节点指向树根(因为我们并不关心这棵树的具体形态)。
因此,我们将 f i n d \mathtt{find} find操作改一改:
int find(int x)
{
if(father[x]!=x)return father[x]=find(father[x]);
return x;
}
这种方法时间复杂度为 O ( α ( n ) ) O(\alpha(n)) O(α(n))。
A C \mathtt{AC} AC代码如下:
时间复杂度: O ( n + m α ( n ) + q α ( n ) ) O(n+m\alpha(n)+q\alpha(n)) O(n+mα(n)+qα(n))
空间复杂度: O ( n ) O(n) O(n)
#include <stdio.h>
int n,m,q,x,y;
int father[10001];
int find(int x)
{
if(father[x]!=x)return father[x]=find(father[x]);
return x;
}
void merge(int x,int y)
{
int fx=find(x);
int fy=find(y);
if(fx!=fy)father[fx]=fy;
}
void init(int x)
{
for(int i=1;i<=x;i++)
father[i]=i;
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
init(n);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
merge(x,y);
}
while(q--)
{
scanf("%d%d",&x,&y);
if(find(x)==find(y))puts("Yes");
else puts("No");
}
return 0;
}