题目链接
题目分析:
注:并不要求集合中两两都有共同爱好,即好友关系具有传递性
输出:
1、输出集合数
2、按降序输出 每个集合的元素个数 (最后需要压缩一下路径)
解题思路:
边输入hobby
边检查,只要user
含有该hobby
就标记(标记为当前user
所在集合的根节点),即hobby[key] = user_root
;
若hobby[key]
已标记,则合并两个集合即可;
标记hobby
表示之后有此hobby
的user
都加入此集合;
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;
}