并查集是一种用来管理元素分组情况的数据结构。并查集可以高效地进行如下操作。不过需要注意并查集虽然可以进行合并操作,但是却无法进行分割操作。
·查询元素a和元素b是否属于同一组。
·合并元素a和元素b所在的组。
并查集的精髓(即它的三种操作,结合实现代码模板进行理解):
1、Make_Set(x) 把每一个元素初始化为一个集合
初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)。
2、Find_Set(x) 查找一个元素所在的集合
查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先!这个才是并查集判断和合并的最终依据。
判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。
合并两个集合,也是使一个集合的祖先成为另一个集合的祖先,具体见示意图
3、Union(x,y) 合并x,y所在的两个集合
合并两个不相交集合操作很简单:利用Find_Set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。
题目大意:SARS(非典型肺炎)传播得非常厉害,其中最有效的办法是隔离那些患病、和患病者接触的人。现在有几个学习小组,每小组有几个学生,一个学生可能会参加多个小组。小组中只要有一个人得病,其余的都是嫌疑人。现在已知这些小组的人员,且0号学生已经患病,求一共有多少个嫌疑人。
分析:每个小组的人员属于同一个集合。在根节点记录每个集合的个数num[i],然后找到0所属的根节点,num[findSet(0)]即为所有的嫌疑人数。
*#include <iostream>
using namespace std;
const int INF=1000000;
const int MAX_N=33333;
int par[MAX_N]; //父亲
int num[MAX_N]; //树的秩记录每个集合的节点个数
int rank[MAX_N]; //树的高度
//初始化n个元素
void init(int n){
for (int i=0; i < n ; i++){
par[i] = i; //使用本身做根
num[i] = 1;
rank[i] = 0;
}
}
//查询树的根
int findroot(int x){
if(par[x] != x)
{
return par[x] = findroot(par[x]);
}
return par[x];
}
//合并x和y所属的集合
void unite(int x,int y){
x=findroot(x);
y=findroot(y);
if(x == y) return;
if(rank[x] < rank[y])
{
par[x] = y;
num[y] += num[x];
} else
{
par[y] = x;
num[x] += num[y];
if(rank[x] == rank[y]) rank[x]++;
}
}
//判断x和y是否属于同一个集合
bool same(int x,int y)
{
return findroot(x) == findroot(y);
}
int n,m;
int main()
{
while(cin>>n>>m&&n)
{
int k,res,vis;
init(n);
while(m--)
{
cin>>k>>vis;
for(int i=1;i<k;i++)
{
cin>>res;
unite(vis,res);
}
}
cout<<num[findroot(0)]<<endl;
}
}