原文链接:http://kakazai.cn/index.php/Kaka/Pat/query/id/197
题目
题目链接:https://pintia.cn/problem-sets/994805342720868352/problems/994805361586847744
When register on a social network, you are always asked to specify your hobbies in order to find some potential friends with the same hobbies. A social cluster is a set of people who have some of their hobbies in common. You are supposed to find all the clusters.
Input Specification:
Each input file contains one test case. For each test case, the first line contains a positive integer N (≤1000), the total number of people in a social network. Hence the people are numbered from 1 to N. Then N lines follow, each gives the hobby list of a person in the format:
Ki: hi[1] hi[2] … hi[Ki]
where Ki (>0) is the number of hobbies, and hi[j] is the index of the j-th hobby, which is an integer in [1, 1000].
Output Specification:
For each case, print in one line the total number of clusters in the network. Then in the second line, print the numbers of people in the clusters in non-increasing order. The numbers must be separated by exactly one space, and there must be no extra space at the end of the line.
Sample Input:
8
3: 2 7 10
1: 4
2: 5 3
1: 4
1: 3
1: 4
4: 6 8 1 5
1: 4
Sample Output:
3
4 3 1
题意分析
假设人A属于社交圈a,人B属于社交圈B,如果人A和人B具有共同爱好,则它们属于同一个社交圈,要把社交圈A和社交圈B合并。
现在有n个人,要找出有多少个社交圈,并将社交圈的人数降序排列。
把社交圈看成集合,人看成元素,问题变成,如果两个元素之间存在关系(这里指它们具有共同爱好),则应该把它们所在的集合合并。因此可以用并查集。
知识点与坑点
- 知识点
1)并查集
- 坑点
1)
一、并查集
算法思路
1 假设每个人一开始独立成为一个社交圈
2 存好每个爱好下的第一个人,合并它与其他该爱好下的人的社交圈
3 做完所有合并后,遍历所有人,并累计它们所在的社交圈的人数
代码-c++版
#include <iostream>
#include<algorithm>
#include<set>
using namespace std;
/*数值范围*/
const int maxn = 1001; //最多有1000个人,1000个爱好
/*所有变量*/
int father[maxn]; //并查集数组
int hobby[maxn]; //存储每个爱好下的第一人
int social[maxn]; //存储社交圈的人数
/* 并查集-初始化*/
void initiate(){
for(int i=0;i<maxn;i++){
father[i]=i;
}
}
/* 并查集-找根结点+压缩路径*/
int findroot(int a){
int x = a;
while(a != father[a]) {
a = father[a];
}
int temp;
while(x != father[x]){
temp = father[x];
father[x] = a;
x = temp;
}
return a;
}
/* 并查集-合并集合+保证根结点编号最小*/
void union_ab(int a,int b){
int fa = findroot(a);
int fb = findroot(b);
if(fa <= fb){
father[fb] = fa;
}else{
father[fa] = fb;
}
}
/* 比较规则 */
int cmp(int a, int b) {
return a > b;
}
int main(){
initiate();
int n;
scanf("%d",&n);
/* 存好每个爱好下的第一个人,合并它与其他该爱好下的人的社交圈 */
int k,h1;
for (int i = 1; i <= n; i++) {
scanf("%d:", &k);
for (int j = 0; j < k; j++) {
scanf("%d", &h1);
if (hobby[h1] == 0) { //i为h1爱好下的第一个人
hobby[h1] = i;
}
else { //i是h1爱好下的其他人
union_ab(hobby[h1],i); //合并第一个人与其他人的社交圈
}
}
}
set<int> root; //存好所有的根结点
for (int i = 1; i <= n; i++) {
int fa = findroot(i);
root.insert(fa);
social[fa]++; //该人所在的社交圈人数+1
}
sort(social + 1, social + 1 + n, cmp); //按社交圈人数降序
/* 按要求输出 */
printf("%d\n", root.size()); //社交圈个数
printf("%d", social[1]);
for (int i = 2; social[i] != 0 && i <= n; i++)
printf(" %d", social[i]);
return 0;
}
代码-python版
#!/usr/bin/python3
#code-python(3.6)
# 并查集-初始化
father = []
for i in range(1001):
father.append(i)
#并查集-找根结点
def findroot(a):
while(father[a]!=a):
a = father[a]
return a
#并查集-合并集合+保证根结点最小
def union_ab(a,b):
fa = findroot(a)
fb = findroot(b)
if(fa <= fb):
father[fb] = fa
else:
father[fa] = fb
#存好每个爱好下的第一个人,并合并它与其他该爱好下的人的社交圈
n = int(input())
hobby = {} #存好每个爱好下的第一个人
for i in range(1,n+1):
line = input().split(" ") #接受每行,并用空格分开
line = line[1:] #不要第一个字符串,即爱好人数
line = list(map(int,line)) #将字符串转为整数
for h in line: #遍历该行所有爱好
if h not in hobby: #该爱好第一次出现
hobby[h] = i #i是该爱好的第一人
else: #该爱好不是第一次出现,即该爱好下已经有人
union_ab(hobby[h],i) #合并第一个人与其他人的社交圈
#找出所有社交圈及其人数,并排序
root = {} #存好所有的根结点和对应人数
for i in range(1,1+n):
root_i = findroot(i)
if root_i not in root:
root[root_i] = 0
root[root_i] += 1
x = sorted(root.items(),key = lambda x:(-x[1]))
#按要求输出
print(len(root))
social = [] #存好社交圈的人数
for i in x:
social.append(str(i[1]))
print(' '.join(social))