1107 Social Clusters (30 分) 【PAT甲级 非传统并查集 C++写法】

网上统统都是用的传统并查集的模板,说实话我不太喜欢那种并查集的写法,于是研究了一个下午终于写出来了自己满意的并查集写法(其实更像模拟),而且速度与内存也比较优秀。


关键是:【一个人所在的社交团体】和【他喜欢的每一个爱好的所有爱好者所在的社交图体】同属于一个Social Cluster
也就是说:让【一个人所在的集合】和【他喜欢的每一个爱好的所有爱好者所在的集合】合并(所有爱好者会包括自己,处不处理都没关系,跳过会更快),就是我们的任务。


不用传统的并查集

  • hobby_lover邻接表来存储某一个爱好的所有爱好者
  • person_hobby邻接表来存储某一个人的所有爱好
  • map来创建[集合编号:集合]的映射(命名为Cluster),用第一个进入集合的人的编号来作为集合的编号,也就是Cluster中的键(也可以理解为并查集中的根节点)),在并查集中不同集合不会有相交元素,所以我用vector来代替set了;
  • setID数组来记录每一个人所在的集合的编号(也可以理解为并查集中的根节点)
  • 对于并查集中的findFather()查找一个节点所在集合的根节点的操作,就可以直接用setID[某一个人的编号](查到的其实是集合的编号)------- 而查找两个元素是否是在同一个集合就可以用setID[a] == setID[b]来判断
  • 对于并查集的两个集合的Union()合并操作,其实将两个集合的vector直接push_backpop_back以及取back处的元素就可以完成合并了(并查集中这两个集合不会有相交的元素的)
# include <bits/stdc++.h>
using namespace std;

int N;
vector<int> hobby_lover[1010];  // 某一个爱好的每一个爱好者
vector<int> person_hobby[1010]; // 某一个人的每一个爱好
map<int, vector<int> > Cluster; // 以第一个加入集合的人作为集合的编号(键),值为一个社交团体集合
vector<int> setID(1010, 0); // 存放每个人所在的集合编号,为0说明集合不存在(这个人不存在于任何集合)

int main(){
    // 输入
    cin >> N;
    for(int person = 1;person <= N;++person){
        int K;
        scanf("%d: ", &K);
        for(int j = 0;j < K;++j){
            int hobby;
            cin >> hobby;
            hobby_lover[hobby].push_back(person); 
            person_hobby[person].push_back(hobby);
        }
    }
    // 算法开始
    for(int person = 1;person <= N;++person){ // 遍历每一个人
    	// 【这个人】指代每一次的【person】
    	// 如果【这个人】没在任何集合那就让它自创一个集合,
    	// 于是他就一定是集合的根节点,于是它的编号也就是person值就是它所属集合的编号
        if(setID[person] == 0){ 
            setID[person] = person;
            Cluster[setID[person]].push_back(person);
        }
        for(int hobby: person_hobby[person]){ // 遍历【这个人】的每一个爱好
            // 《某个爱好者》指代每一次的【lover】
            for(int lover: hobby_lover[hobby]){ // 遍历【这个人】的某一个爱好的所有爱好者,让【这个人】和对应的爱好者并入一个集合
                if(setID[lover] == 0){ // 如果《某个爱好者》没在任何集合,那就让他并入【这个人】所在的集合
                    setID[lover] = setID[person];
                    Cluster[setID[person]].push_back(lover);
                } 
                else { // 如果《某个爱好者》有所在集合,那就将《某个爱好者》所在的集合并入【这个人】所在的集合
                	if(setID[person] == setID[lover]) continue; // 如果两个要合并的集合是同一个集合,那就跳过
                    for(int i = Cluster[setID[lover]].size() - 1;i >= 0 ;i--){
                        int inp = Cluster[setID[lover]].back(); // 取得《某个爱好者》所在集合的成员
                        Cluster[setID[person]].push_back(inp);
                        Cluster[setID[lover]].pop_back();
                    }
                    Cluster.erase(setID[lover]); // 去掉被合并的集合
                    setID[lover] = setID[person]; // 合并后记得更新《某个爱好者》所在集合的编号为【这个人】的集合
                }
            }
        }
    }

    vector<int> rst;
    cout << Cluster.size() << endl;
    for(auto s: Cluster){
        rst.push_back(s.second.size()); // 统计每个社交团体的数量 
	}
    sort(rst.begin(), rst.end(), greater<int>());
    for(int i = 0;i < rst.size();++i){
        cout << rst[i] << (i == rst.size()-1 ? "\n" : " ");
    }
    
    return 0;
}

在这里插入图片描述

/*
hobby people
[1] : 7
[2] : 1
[3] : 3 5
[4] : 2 4 6 8
[5] : 3 7
[6] : 7
[7] : 1
[8] : 7
[9] :
[10] : 1
*/

/*
people hobby
1 : 2 7 10
2 : 4
3 : 5 3
4 : 4
5 : 3
6 : 4
7 : 6 8 1 5
8 : 4
*/

传统并查集

一开始每一个人就是一个集合,每次都合并有相同爱好的人
每次的person和hobby_person[hb]都是有相同爱好hb的,所以每次将他们合并

# include <bits/stdc++.h>
using namespace std;

int N;
int father[1010];
int isRoot[1010];
int hobby_person[1010] = {0};

int findFather(int v){
    if(v == father[v]) return v;
    
    father[v] = findFather(father[v]);
    return father[v];
}

void Union(int a, int b){
    int fa = findFather(a);
    int fb = findFather(b);
    if(fa != fb) father[fa] = fb;
}

void init(){
    for(int i = 1;i <= N;++i){
        father[i] = i;
        isRoot[i] = false;
    }
}

int main(){
    cin >> N;
    init();
    int K, hb;
    for(int person = 1;person <= N;++person){ // 对每个人
        scanf("%d: ", &K);
        for(int j = 0;j < K;++j){ // 对某个人的每个爱好
            cin >> hb;
            if(hobby_person[hb] == 0) // 让这个爱好的第一个爱好者成为一个社交圈子
                hobby_person[hb] = person;
            else // 合并有相同爱好hb的两个社交圈子
             	// Union(person, hobby_person[hb]); // 这么写也没问题,毕竟在Union函数中都会找到father的
             	Union(person, findFather(hobby_person[hb]));
        }
    }
    for(int i = 1;i <= N;++i)
        isRoot[findFather(i)]++;
    int cnt = 0;
    for(int i = 1;i <= N;++i)
        if(isRoot[i] != 0)
            cnt++;
    cout << cnt << endl;
    sort(isRoot+1, isRoot+N+1, greater<int>());
    for(int i = 1;i <= cnt;++i)
        cout << isRoot[i] << (i == cnt?'\n':' ');
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值