并查集:擒贼先擒王
Posted lyfoi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并查集:擒贼先擒王相关的知识,希望对你有一定的参考价值。
定义
并查集,在一些有(N)个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间((1)~(3)秒)内计算出试题需要的结果,只能用并查集来描述。摘自百度百科。
实现
有(A,B,C,D)四个人,他们各自为王,一天:
(A)与(B)打架了,然后(A)战胜了(B),于是(B)成了(A)的小弟,于是数组(F(B)=A),代表(B)的老大是(A);
然后(C)与(D)打架了,(C)打败了(D),(D)成了(C)的手下,于是数组(F(D)=C),代表(D)的老大是(C);
下面,(C)的手下D与A打架了,(A)战胜了(D),于是(D)要归顺(A);
(C)一看就不干了,这是我的人啊,不过后来为王,于是(A)不但收服(D),还然(C)成为了他的小弟,于是数组(F(C)=A),代表(C)的老大是(A)。
大概是如上的,这个过程我们用语言描述为:
并查集通过一个一维数组来实现,本质上是维护一个森林。刚开始的时候,森林里的每一个结点都是一个集合(也就是只有一个结点的树,是孤立的),之后根据题意,逐渐将一个个集合合并(也就是合并成一棵大树)。之后寻找时不断查找父节点,当查找到父结点为本身的结点时,这个结点就是祖宗结点。合并则是寻找这两个结点的祖宗结点,如果这两个结点不相同,则将其中右边的集合作为左边集合的子集(即靠左,靠右也是同一原理)。
所以说我们要一个找老大的函数,通过递归实现
int find(int k){
if(f[k]==k)return k;//如果找到了老大
return find(f[k]);//否则继续找
}
上面的代码不是很靠谱,可能会(TLE),所以要用到路径压缩。路径压缩就是当(B)归顺A前,(C)还是(B)的老大,那么我们不仅要(F(D)=A),还要(F(F)=A),把所有节点的祖先直接定为最高的祖先,就是路径压缩。代码非常的简单:
int find(int k){
if(f[k]==k)return k;
return f[k]=find(f[k]);
}
下面就简单了,把每个人的老大设置为自己:
for(i=1;i<=n;i++)
f[i]=i;
判断两个人是否是一个老大:
if(find(a)==find(b))
让一个人臣服另一个人:
f[find(B)]=find(A);//B打赢了A,于是让A的一切关系归属B的老大
现在你基本学会了并查集了,那么让我们做一道板子题目:
例题
Code
#include<bits/stdc++.h>
using namespace std;
int n,m,f[10010];
int find(int k)
{
if(f[k]==k)return k;
return f[k]=find(f[k]);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
f[i]=i;
for(int i=1;i<=m;i++)
{
int x,A,B;
cin>>x>>A>>B;
if(x==1)
f[find(B)]=find(A);
else
if(find(A)==find(B))
printf("Y
");
else
printf("N
");
}
return 0;
}
以上是关于并查集:擒贼先擒王的主要内容,如果未能解决你的问题,请参考以下文章