引言
无所不在的宗教
世界上宗教何其多。假设你对自己学校的同学总共有多少种宗教信仰很感兴趣。已知学校有n个学生,但是建议你不要直接问大家的宗教信仰,这不是一种礼貌的做法。还有另外一个方法是问m对同学,问他们是否信仰同一宗教。根据这些信息,聪明的你如何计算出学校最多有多少种宗教信仰?
概述
Union-Find Set
一种树型数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题
在使用中通常以森林来表示
初始化:将编号分别为1…N的N个元素划分为N个不相交集合,在每个集合中,选择其中某个元素代表所在集合(通常用根结点来作为代表)
常见操作:
•**合并**两个集合
•**查找**某元素属于哪个集合
•判断两个元素是否属于同一个集合
基本操作
判断两个元素是否属于同一集合,只要判断它们所在集合的根结点是否相同即可
合并两个集合,将一个集合的根结点作为另一个集合的根结点
基本函数
make_set(x):把每一个元素初始化为一个集合【将每个元素的父结点初始化为自己】
find_set(x):查找一个元素所在的集合。在执行查找操作时,要沿着父结点一直找下去,直到找到根结点为止
union_set(x, y):利用find_set()找到两个集合的根结点,将一个集合的根结点指向另一个集合的根结点
启发式策略优化
按秩合并
•秩(Rank):结点所在树的高度,只有一个根结点的树的秩为0
•union_set(x,y)时按秩合并,合并时将秩相对较小的树合并到秩相对较大的树中,这样合并之后树的高度会相对较小
路径压缩
•find_set(x)时路径压缩,使用“递推”找到根结点后,在“回溯”时将路径上所有结点的父结点都直接指向根结点,以后再次调用find_set(x)时其时间复杂度为O(1)
#include <stdio.h>
const int MAXN = 100; /*结点数目*/
int pa[MAXN]; /*pa[x]表示x的父结点*/
int rank[MAXN]; /*rank[x]是x的高度的一个上界*/
/*创建一个单元集*/
void make_set(int x)
{
pa[x] = x;//自己为自己的父节点,
rank[x] = 0;//初始只有自己一个节点,秩为0
}
/*带路径压缩的查找*/
int find_set(int x)
{
if(x != pa[x])//自己的父节点不是自己,就继续找,知道找到自己是自己父亲
pa[x] = find_set(p[x]); //将所有结点的父结点回溯为根结点
return pa[x];
}
/*按秩合并x,y所在的集合*/
void union_set(int x, int y)
{
x = find_set(x); //返回x的根结点
y = find_set(y); //返回y的根结点
if(rank[x] > rank[y])/*让rank比较高的作为父结点*/
pa[y] = x;//x的秩高,x做父亲,y做x的儿子
else
{
pa[x] = y;//否则,x的父亲就是y
//秩相同,其实谁做父亲都一样,但是这里已经先让y父亲了,y的秩就会加1
if(rank[x] == rank[y])
rank[y]++;
}
}
实例:PTA 朋友圈(25 分)
朋友圈(25 分)
某学校有N个学生,形成M个俱乐部。每个俱乐部里的学生有着一定相似的兴趣爱好,形成一个朋友圈。一个学生可以同时属于若干个不同的俱乐部。根据“我的朋友的朋友也是我的朋友”这个推论可以得出,如果A和B是朋友,且B和C是朋友,则A和C也是朋友。请编写程序计算最大朋友圈中有多少人。
输入格式:
输入的第一行包含两个正整数N(≤30000)和M(≤1000),分别代表学校的学生总数和俱乐部的个数。后面的M行每行按以下格式给出1个俱乐部的信息,其中学生从1~N编号:
第i个俱乐部的人数Mi(空格)学生1(空格)学生2 … 学生Mi
输出格式:
输出给出一个整数,表示在最大朋友圈中有多少人。
输入样例:
7 4
3 1 2 3
2 1 4
3 5 6 7
1 6
输出样例:
4
思路:一看题目就发现是个并查集的板子,有着稍微的改动,在合并完所有的节点后,遍历所有节点的父节点,并记录该父节点当前做父亲数,并比较其当前是不是儿子最多的父亲,把最大的儿子数记录下来,输出即可。
上代码
#include "bits/stdc++.h"
using namespace std;
const int maxx = 30005;
int father[maxx];
int ran[maxx];
int isRoot[maxx];
void make_set(int x)
{
father[x] = x;
ran[x] = 0;
}
int find(int x)
{ //压缩路径的查找
if (father[x] == x)
{ //如果自己的父节点是自己,那么x 结点就是根结点
return x;
}
return find(father[x]); // 返回父结点的根结点
}
void merge(int x, int y)
{
x = find(x);
y = find(y);
if (x == y)
return;
if (ran[x] > ran[y])
{
father[y] = x;
}
else
{
father[x] = y;
if (ran[x] == ran[y])
{
ran[y]++;
}
}
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 0; i <= n; i++)
{
make_set(i);
}
int x, y;
int k;
int s = 1;
for (int i = 0; i < m; i++)
{
cin >> k >> x;
for (int j = 0; j < k - 1; j++)
{
cin >> y;
merge(x, y);
}
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
int f = find(i);
isRoot[f]++;
ans = max(ans, isRoot[f]);
}
cout << ans << endl;
}