题目来源:http://noi.openjudge.cn/ch0403/1526/
1526:宗教信仰
总时间限制: 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
-----------------------------------------------------
思路
题意:已知图中节点的连接关系,求图中的连通支数量。
并查集模板题。关于并查集的详细介绍,可以看这篇神奇的博文一个很有意思的并查集详解
-----------------------------------------------------
代码
// 并查集 #include<iostream> #include<fstream> using namespace std; const int NMAX = 50005; int pre[NMAX] = {}; // 每个节点的父节点 int n = -1; // 全局变量:学生人数 int cnt = n; // 全局变量:总的集合个数 int find(int x) // 输入一个节点的编号,返回该节点在并查集中的根节点 { int r = pre[x],t = x; while (r!=t) // 如果不是根节点(父节点==自己),则不断上溯 { t = r; r = pre[t]; } // 路径压缩过程 t = x; int tt = x; while (t!=r) // 从叶子节点开始回溯,把路径上的所有节点的父节点全都设为根节点 { tt = pre[t]; pre[t] = r; t = tt; } return r; } void join(int x, int y) // 合并两个元素所在集合 { int xx = find(x), yy = find(y); // 分别找到两个元素的根节点 if ( xx!= yy) // 如果两个元素的根节点不同, 则需要合并; 否则两个元素本来就在同一个集合 { cnt--; // 总的集合个数-1 pre[xx] = yy; // 将一个集合的根节点的父节点设为另一个集合的根节点 } } int main() { #ifndef ONLINE_JUDGE ifstream fin ("0403_1526.txt"); int m,i,x,y,t=0; while (fin >> n >> m) { if (n==0 && m==0) { break; } for (i=0; i<n; i++) { pre[i] = i; } cnt = n; for (i=0; i<m; i++) { fin >> x >> y; join(x,y); } cout << "Case "<< (++t) << ": " << cnt << endl; } fin.close(); #endif #ifdef ONLINE_JUDGE int m,i,x,y,t=0; while (cin >> n >> m) { if (n==0 && m==0) { break; } for (i=0; i<n; i++) { pre[i] = i; } cnt = n; for (i=0; i<m; i++) { cin >> x >> y; join(x,y); } cout << "Case "<< (++t) << ": " << cnt << endl; } #endif }