poj2524 Ubiquitous Religions

照例先上题目

8:宗教信仰

  • 查看
  • 提交
  • 统计
  • 提问
总时间限制: 
5000ms 
内存限制: 
65536kB
描述
世界上有许多宗教,你感兴趣的是你学校里的同学信仰多少种宗教。
你的学校有n名学生(0 < n <= 50000),你不太可能询问每个人的宗教信仰,因为他们不太愿意透露。但是当你同时找到2名学生,他们却愿意告诉你他们是否信仰同一宗教,你可以通过很多这样的询问估算学校里的宗教数目的上限。你可以认为每名学生只会信仰最多一种宗教。


输入
输入包括多组数据。
每组数据的第一行包括n和m,0 <= m <= n(n-1)/2,其后m行每行包括两个数字i和j,表示学生i和学生j信仰同一宗教,学生被标号为1至n。输入以一行 n = m = 0 作为结束。
输出
对于每组数据,先输出它的编号(从1开始),接着输出学生信仰的不同宗教的数目上限。
样例输入
10 9
1 2
1 3
1 4
1 5
1 6
1 7
1 8
1 9
1 10
10 4
2 3
4 5
4 8
5 8
0 0
样例输出
Case 1: 1
Case 2: 7







































并查集(Union-find)的实现。附加了两点小技巧有效地降低了树的高度,使find与union操作的时间复杂度都接近于O(1):

  1. 在find操作中加入了路径压缩:当前结点原来指向其父,现在指向其爷爷。
  2. 在union操作中兼顾了树的平衡:总是让规模小的树成为规模大的树的子树。
如果不清楚什么是并查集,我转载了一篇非常好的并查集讲解:并查集(Union-Find)算法介绍;另有我对原文的总结。

代码清单
//并查集Union-find

#include <iostream>
using namespace std;

#define MAXN 50005

int fa_info[MAXN];	//储存结点的父亲在数组中的下标
int size[MAXN];	//每个结点都看成一棵树,那么这棵树有多少个结点?

int find_with_compression(int elem)	//带路径压缩的查找,输入待查找的元素,返回它的始祖
{
	while (elem != fa_info[elem])	//不是根结点
	{
		fa_info[elem] = fa_info[fa_info[elem]];	//当前结点原来指向父亲,现在指向爷爷,此为路径压缩
		elem=fa_info[elem];
	}

	return elem;
}

void union_with_balanced(int p, int q, int *set_count)
{
	int i=find_with_compression(p);	//p的始祖是i
	int j=find_with_compression(q);	//q的始祖是j

	if (i==j) return;	//有相同的始祖,说明之前合并过的,直接return

	if(size[i]>size[j])	//j树的规模比较小,于是要做i的子树;此为建立平衡树
	{
		fa_info[j]=i;
		size[i] += size[j];
	}
	else
	{
		fa_info[i]=j;
		size[j] += size[i];
	}

	--(*set_count);	//每合并一次,集合数减1
}

int main()
{
	//freopen("D:\\in.txt", "r", stdin);
	//freopen("D:\\out.txt", "w", stdout);

	int n, m, i, serial=0;
	int stu1, stu2;

	while (1)
	{
		cin>>n;
		cin>>m;

		if(m==0 && n==0) break;

		++serial;
		//初始化父结点表和树的尺寸表
		for (i=0; i<n+1; ++i)
		{
			fa_info[i]=i;//初始时自己是自己的父亲,有n棵树
			size[i]=1;//初始时每棵树的规模都是1
		}

		for (i=0; i<m; ++i)
		{
			cin>>stu1;
			cin>>stu2;

			union_with_balanced(stu1, stu2, &n);
		}

		cout<<"Case "<<serial<<": "<<n<<endl;
	}

	return 0;
}



你可能感兴趣的:(UNION,find,并查集,Union-Find,poj2524)