题意
多组样例,每组输入样例n,m表示有n个人,有m对数据,接着是m对人,每一对人表示他们在一个阵营里,问最后这n个人中一共有几个阵营(没有出现的人算是一个单独阵营)
m和n都为0时表示结束
输入样例
10 9 //test1
1 2
1 3
1 4
1 5
1 6
1 7
1 8
1 9
1 10
10 4 //test 2
2 3
4 5
4 8
5 8
0 0
输出样例
Case 1: 1
Case 2: 7
分析:这是一道典型的并查集应用。
并查集最常见的是用树实现。一开始所有元素都自成一树,每次碰到一对,就把其中一个元素的祖先改成另外一个,也就是把他们放到一个树里。这是“并”。查的话就是找到元素的祖先,如果两个元素的祖先是一个元素,那么就是一个集合,否则就是两个集合。
需要注意的是并查集有两个优化。 一是按秩(rank)合并 二是路径压缩。
按秩合并就是说在合并两个集合时,总是让较小深度的集合加入较深深度的集合。若两个集合深度相同,则让一个加入另一个,同时rank++。
路径压缩就是为了避免找一个元素的祖先时经过的路径过长,数的深度越小越好(深度为2时正好查询复杂度为O1)。所以,在找一个元素的祖先的时候,经过的元素肯定都是这个元素祖先的元素。所以在找的同时不妨都把它们连到祖先上。
下面放并查集模板。
int par[maxn]; //元素的祖先
int dep[maxn]; //树的深度
//初始化n个元素
void _init(int n)
{
for(int i = 0 ; i < n ; i ++){
par[i] = i; //各自成树
dep[i] = 1; //各个树的深度都为1
}
}
//查询
int _find(int x)
{
if(par[x] == x) return x;
else return par[x] = _find(par[x]); //路径压缩 利用栈的特性,把路径上所有元素都变成祖先的儿子
}
//合并
void _unite(int x,int y)
{
x = _find(x); //找到x的祖先
y = _find(y); //找到y的祖先
if(x == y) return; //如果祖先相同,那么不用合并
if(dep[x] < dep[y]) par[x] = y; //合并到深度较高的树
else{
par[y] = x;
if(dep[x] == dep[y]) dep[x] ++; //如果两树深度相同 dep++
}
}
到这里这题就自然解出了,最后扫一遍判断有多少树就可以了(par[i] = i 则说明i是一个树的祖先)。