toj2469 Friends

题目链接:http://acm.tju.edu.cn/toj/showp2469.html

题目大意:给定一些人,如果他们不是朋友,就不能住在一间房间。朋友间的关系是可以传递的,即A和B是朋友,B和C是朋友,那么A和C也是朋友。 给定人数和他们的关系,问至少需要多少个房间;

思路:画个示意图,瞬间发现是求连通分量个数的问题,直接并查集,最终连通分量的个数即为房间数。用并查集的关键在于实现以下几个函数:Make, Find, Union!下面copy一段关于并查集的定义和使用(如果你对并查集不熟的话,可以看看)

l         并查集:(union-find sets)

一种简单的用途广泛的集合并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数等。最完美的应用当属:实现Kruskar算法求最小生成树。

l         并查集的精髓(即上面讲的三种操作):

1、Make_Set(x) 把每一个元素初始化为一个集合

初始化后每一个元素的父亲节点是它本身,每一个元素的祖先节点也是它本身(也可以根据情况而变)。

2、Find_Set(x) 查找一个元素所在的集合

查找一个元素所在的集合,其精髓是找到这个元素所在集合的祖先!这个才是并查集判断和合并的最终依据。
判断两个元素是否属于同一集合,只要看他们所在集合的祖先是否相同即可。

合并两个集合,也是使一个集合的祖先成为另一个集合的祖先,具体见示意图

3、Union(x,y) 合并x,y所在的两个集合

合并两个不相交集合操作很简单:
利用Find_Set找到其中两个集合的祖先,将一个集合的祖先指向另一个集合的祖先。

l         并查集的优化

1、Find_Set(x)时 路径压缩
寻找祖先时我们一般采用递归查找,但是当元素很多亦或是整棵树变为一条链时,每次Find_Set(x)都是O(n)的复杂度,有没有办法减小这个复杂度呢?
答案是肯定的,这就是路径压缩,即当我们经过"递推"找到祖先节点后,"回溯"的时候顺便将它的子孙节点都直接指向祖先,这样以后再次Find_Set(x)时复杂度就变成O(1)了,如下图所示;可见,路径压缩方便了以后的查找。

2、Union(x,y)时 按秩合并
即合并的时候将元素少的集合合并到元素多的集合中,这样合并之后树的高度会相对较小。


结合本题的代码,你会有一个更加深刻的理解:

ps:在第二遍做这道题的时候, 犯了一个致命的细节错误,问题时测试用例还没有测出来, 因为数组下标表示的是人的标号  而人的标号是从1开始的  所以再循环时,标号不能从0开始!!

#include <iostream>
#include <cstring>
using namespace std;
struct people               //最近对数组无感  发现struct很好用的
{
    int ancestor;
}people[102];

int find(int i)                //!!关键 find 函数  不断通过 j=people[j].ancestor来找祖先
{
  int j = i;
  while(1)
  {
    if(people[j].ancestor == -1)
    {
       if(j!=i)
       people[i].ancestor = j;     //在返回祖先时别忘了把该节点的祖先设置为找到的祖先
       return j;
    }
    j = people[j].ancestor;
  }
}

int main()
{
  int cas;
  cin>>cas;
  while(cas--)
  {
    int n,i,m;
    cin>>n>>m;
    for(i=1;i<=n;i++)
    {
        people[i].ancestor=-1;   //关键 make函数 :初始化每个节点的祖先都是-1,在find函数中判断为-1时,返回祖先本身;
    }
     for(i=0;i<m;i++)
     {
        int a,b,x,y;
        cin>>a>>b;
        x = find(a);
        y = find(b);
        if(x!=y)
        {
        people[x].ancestor = y;  //关键 union函数: 如果两个节点的祖先不同,因为有朋友关系,所以把一个的祖先设置为另一个节点
        }
     }
     int ans = 0;
     for(i=1;i<=n;i++)            //下标从1开始啊 魂淡
     {
        if(people[i].ancestor == -1)    //多少个根  跟少个连通分量  即多少间房
        ans++;
     }
     cout<<ans<<endl;
  }
  return 0;
}






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值