【ACM】PAT.A1107 Social Clusters【并查集】

题目链接
题目分析:

注:并不要求集合中两两都有共同爱好,即好友关系具有传递性
输出:
1、输出集合数
2、按降序输出 每个集合的元素个数 (最后需要压缩一下路径)

解题思路:

边输入hobby边检查,只要user含有该hobby就标记(标记为当前user所在集合的根节点),即hobby[key] = user_root
hobby[key]已标记,则合并两个集合即可;

标记hobby表示之后有此hobbyuser都加入此集合;


AC程序(C++):
/**************************
*@Author: 3stone
*@ACM: PAT.A1107 Social Clusters
*@Time: 18/6/12
*@IDE: VS Code
***************************/
#include<cstdio>
#include<algorithm>
#include<cstring>

#define maxn 1005

using namespace std;

int user[maxn];  //并查集
int num_set[maxn]; //每个集合中元素数
int hobby_set[maxn]; //标记hobby所属集合

//初始化
void initiate(int n) {
    for (int i = 0; i <= n + 1; i++)
        user[i] = i;
}

//查找根节点(迭代版)
int find_root(int a) {
    int root = a;
    while(root != user[root])
        root = user[root];

    //路径压缩(再回溯一遍,把走过结点的父节点全部赋值为根结点)
    while(a != user[a]) {
        int z = a;
        a = user[a];
        user[z] = root;
    }
    return root;
} 

//合并集合
void Union(int a, int b) {
    a = find_root(a);
    b = find_root(b);
    if (a != b)
        user[b] = a; 
    //此处没有路径压缩,
    //最后如果需要统计每个集合中元素的个数,需要额外路径压缩一下
}

int main() {

    int n, num_of_hobby, cur_hobby;
    int cur_root_user;
    int sum_of_set; //集合数
    while(scanf("%d", &n) != EOF) {

        initiate(n);
        memset(hobby_set, 0, sizeof(hobby_set)); //初始时hobby不属于任意集合
        memset(num_set, 0, sizeof(num_set)); //所有集合中元素初始个数为0

        for (int i = 1; i <= n; i++) {//user序号从1开始 

            scanf("%d:", &num_of_hobby);
            cur_root_user = find_root(i); //当前user的根节点

            for(int j = 0; j < num_of_hobby; j++) {//逐个输入当前user的hobby

                scanf("%d", &cur_hobby);

                //此hobby首次出现
                if(hobby_set[cur_hobby] == 0)//标记hobby所属集合
                    hobby_set[cur_hobby] = cur_root_user;

                //此hobby之前已出现,则当前user所属集合需要与另一集合合并
                else {
                    if(cur_root_user != hobby_set[cur_hobby]) { //当前hobby与不属于当前user,合并
                        Union(cur_root_user, hobby_set[cur_hobby]);
                        hobby_set[cur_hobby] = cur_root_user; //更新hobby标记
                    }
                }//else

            }//for - j

        }//for - i

        //由于之前的Union()中不含有路径压缩,所以用根节点统计集合中元素个数不正确
        //先路径压缩一下
        for (int i = 1; i <= n; i++) {
            find_root(i);
        }

        //统计结果
        sum_of_set = 0;
        for(int i = 1; i <= n; i++) {
            num_set[user[i]]++; 
            //借鉴《算法笔记》解法,此处改为num_set[find_root(i)]++;则上一步路径压缩就可以省去了;
            //其实我的做法更省事一点
            if (i == user[i])
                sum_of_set++;
        }

        printf("%d\n", sum_of_set); //集合数

        //集合元素个数需要降序输出
        sort(num_set + 1, num_set + n + 1);
        printf("%d", num_set[n]);
        for (int i = n - 1; num_set[i] != 0; i--) {
            printf(" %d", num_set[i]);
        }
        printf("\n");

    }//while

    return 0;
}


【以下正确解法 转自《算法笔记》并查集】

#include<cstdio>
#include<cmath>
#include<algorithm>
#define maxSize 1010
using namespace std;

int father[maxSize];
int course[maxSize] = {0};
int isRoot[maxSize] = {0}; //记录每个集合的元素个数 

void init(int n){//初始化 
    for(int i = 1; i <= n; i ++){
        father[i] = -1;
        isRoot[i] = false;
    }
}

int findF(int x){//寻找根节点 
    if(-1 == father[x])
        return x;
    else{
        int temp = findF(father[x]);
        father[x] = temp;//状态压缩 
        return temp;
    } 
}

void Union(int a, int b){//合并集合 
    int ta = findF(a);
    int tb = findF(b);
    if(ta != tb)
        father[ta] = tb;
}

bool cmp(int a, int b){
    return a > b;
}

int main(){
    int n, k, h;
    while(scanf("%d", &n) != EOF){
        init(n);
        for(int i = 1; i <= n; i++){
            scanf("%d:", &k);

            for(int j = 1; j <= k; j++){
                scanf("%d", &h);
                if(0 == course[h]){
                    course[h] = i;
                }
                Union(i, findF(course[h]));//爱好相同,合并集合 
            }
        } 

        for(int i = 1; i <= n; i++){//统计人数 
            isRoot[findF(i)]++;
        } 

        int ans = 0;
        for(int i = 1; i <= n; i++){
            if(isRoot[i] != 0)
                ans++;
        }
        printf("%d\n", ans);
        sort(isRoot+1, isRoot + n + 1, cmp);//不能在[1-ans]排,不能保证根全 
        for(int i = 1; i < ans;i++)
            printf("%d ", isRoot[i]);
        printf("%d\n", isRoot[ans]);

    }//while

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值