目录
题面
Description
艾斯蒂恩优公司最近打算推出一款社交网站,为了让更多用户之间互加好友,项目经理ZZK准备推出多度人脉功能。我们知道,人脉是我们直接的好友,那么多度人脉就是包括好友的好友以及好友的好友的好友等等,也就是除了直接好友之外能间接认识的所有好友。现在,ZZK给你网站数据库里所有的用户关系信息,希望你能将所有用户的多度人脉统计出来,并制作一个排行榜,看谁的多度人脉最多!
Input
第1行为该网站所有账户的个数n以及所有账户之间好友关系的个数m(1<=n<=4000,1<=m<=40000)
第2行到第m+1行为账户之间的好友关系,每行包括两个账户名,表示这两个账户互为好友,给定的数据不会有重复的关系(A B与B A是一个关系)。其中账户名最长为20个字符,全部为英文字母,账户名区分大小写。
Output
输出n行,每行包括一个多度人脉数量,多度人脉从大到小进行排序。
Sample Input
6 5
SYC ZZK
ZZK WC
SYC WC
SYC LG
WZ LZC
Sample Output
2
1
1
0
0
0
分析
这题算是SDNUOJ上比较难的一个并查集系列的题了,要做这一题,可能需要有一点图论的知识,不过想象力比较强大的人应该一看就懂了。
要求的是多度人脉的数量,不用和名字对应,而且从大到小输出,那么我们只负责计算就行了。这题肯定要和并查集挂钩,我们先来考虑人脉关系在并查集中的存储。以给出的输入为例。并查集使用一个树的结构来存储元素之间的关系,不过这颗或者几颗树的结构并不能完好的保存原有图的信息,因为并查集有时采用按秩合并或者路径压缩之类的算法,或者仅仅是因为草草地合并两颗树这种操作。
对于题中给出的样例,我们来作个“图”。
可见,图中有两个集合,在同一个集合中的人可以互相认识,而像LG与LZC这样不在一个集合中的,则不能认识。以求LG的多度人脉为例,我们能一眼看出,LG能认识的有3人,而能直接认识的,只有1人,所以LG的多度人脉为2。多算几次多度人脉之后,我们得出这样一个规律。
某人的多度人脉 = 他所在集合中总人数 - 1(除去他自己) - 直接好友数量;
我们每次合并时让根节点记录当前集合的大小
vector <int> parent;//记录父节点
vector <int> size;//记录集合权值
void mere(int x, int y)
{ //合并两个人所在集合
int rootx = find(x),rooty = find(y);
parent[rootx] = rooty;
size[rooty] += size[rootx];
//集合大小用根节点序号root对应的size[root]记录
}
而直接好友数由题目关系信息可以得到。
for(int i = 1; i <= m; i++)
{
string str1,str2;
cin >> str1 >> str2;//输入两个人名
int n1 = dset.add(str1);//加入并查集并分配序号
int n2 = dset.add(str2);
if(str1 != str2)//注意:题目中可能出现“A是A的朋友”这种数据,
{ //因此要特别判断一下,不然WA死QAQ
dset.friends[str1]++;//用一个map存直接好友信息
dset.friends[str2]++;//每次输入都加一次直接好友数量
}
if(!dset.same(n1,n2))
{
dset.mere(n1,n2);
}
}
AC代码
//
//Created by Zhangfenglai 2022/10/22,23:25
//
#include <bits/stdc++.h>
#define henshin ios_base::sync_with_stdio(false),cin.tie(0);
using namespace std;
class dsu //并查集 desuyo!
{
private:
vector <int> parent;
vector <int> size;
public:
map <string, int> seqn;//附加(并非原并查集的一部分):每人一个序号
map <string, int> friends;//附加(并非原并查集的一部分):记录每人的直接好友数量
explicit dsu(int siz): parent(siz + 5),size(siz + 5,1)
{ //初始化并查集
for(int i = 0; i < siz + 5; i++)
{
parent[i] = i;
}
}
int siz(int x)
{ //返回序号为x的人所在集合大小
return size[find(x)];
}
int c = 0;
int add(const string& s)
{ //太懒了所以写了个一键分配序号hhh
if(!seqn.count(s))
{
return seqn[s] = c++;
}else
{
return seqn[s];
}
}
int find(int x)
{ //寻找根节点 这里做了路径压缩↓
return parent[x] == x ? x : (parent[x] = find(parent[x]));
}
void mere(int x, int y)
{ //合并两个人所在集合
int rootx = find(x),rooty = find(y);
parent[rootx] = rooty;
size[rooty] += size[rootx];
//集合大小用根节点序号root对应的size[root]记录
}
bool same(int x,int y)
{ //判断两人是否处在同一集合
return find(x) == find(y);
}
};
int main()
{
henshin
int n,m;
cin >> n >> m;
dsu dset = dsu(n);
for(int i = 1; i <= m; i++)
{
string str1,str2;
cin >> str1 >> str2;
int n1 = dset.add(str1);
int n2 = dset.add(str2);
if(str1 != str2)
{
dset.friends[str1]++;
dset.friends[str2]++;
}
if(!dset.same(n1,n2))
{
dset.mere(n1,n2);
}
}
int ans[n+5];
map<string, int>::iterator x;
x = dset.seqn.begin();
for(int i = 0; i < n; i++)
{
string name = (*x).first;
int num = (*x).second;
ans[i] = dset.siz(num) - dset.friends[name] - 1;
x++;
}
sort(ans,ans + n,greater<int>());
for(int i = 0; i < n; i++)
{
cout << ans[i] << '\n';
}
return 0;
}