题意:有m对集合,每对表示的人有相同的信仰,问在n个人里面最多有多少种不同的信仰 |
并查集思路:
这就像一组组朋友圈,将具有相同属性的划分为一个集合。然后找到有多少集合即可。
实现方法:
我们把每个圈子挑出一个人作为根节点,所有其成员都只认这个人(看成代表),我们叫他父结点,对于每对组合,如果这两个人有相同的父结点(代表者),那么就说明这两个人是相同圈子里面的人。
否则,他们就是不同圈子里面的人,然后这两个不同的圈子,又通过这两个人联系到一起,组成新的一个大集合,这时候不需要两个代表(父结点),只需要将后者的父结点再指向前者的父结点即可,可以形象地表示成圈子B的代表又隶属于圈子A的代表了。这样,只要在这个圈子里面随便挑两个人,问他们的父结点,就又可以追溯到同一个人了(这也是为什么一个代表能表示一个集合的原因了)
具体代码如下,感觉这种压缩路径的思想很赞
#include <iostream>
#include <vector>
#include <cstdio>
#include <map>
#include <climits>
#include <string>
#include <cmath>
#include <cstring>
#include <stack>
#include <queue>
#include <algorithm>
#define maxn 50000+5
using namespace std;
typedef long long ll;
int father[maxn];
int n;
ll m;
void init() //初始化
{
for(int i=1;i<=n;i++)
father[i] = i;
}
int get(int x) //层层向上找父节点,直到到达根部
{
if(father[x]==x)
return x;
return father[x]=get(father[x]); //压缩路径算法
}
void merge(int x, int y) //集合合并
{
int a = get(x);
int b =get(y);
if(a!=b)
father[b] = a;
}
int main()
{
int num = 1;
while(~scanf("%d%lld",&n,&m)&&(n||m))
{
init();
while(m--)
{
int x, y;
cin>>x>>y;
merge(x,y);
}
int ans = 0;
/* std::map<int,int> tofind; //不能这样写,树的深度不一定为2
for(int i=1;i<=n;i++)
{
if(!tofind[father[i]])
{
ans++;
tofind[father[i]] = 1;
}
}*/
for(int i=1;i<=n;i++)
{
if(i==father[i])
{
ans++;
}
}
printf("Case %d: %d\n",num++,ans);
}
return 0;
}