SDNUOJ 1129.多度人脉 (并查集及其扩展应用)

目录

题面

Description

Input

Output

Sample Input

Sample Output

分析

AC代码


题面

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;
}
  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虚引力子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值