浙大 PAT 甲级 1034 Head of a Gang 并查集

之前看王道论坛的机试指南这本书时也碰到过这题,当时没有做出来,现在一边过了~看来练习了一段时间效果还是很明显的,坚持!

这题的核心就是并查集,并查集最直观的作用就是求连通子图。

首先,我们定义一个数组,用双亲表示法来表示各棵树。

int Tree[N];

用Tree[i]来表示节点i的双亲节点,若Tree[i]为-1,则表示该节点不存在双亲节点,即节点i为其所在的树的根节点。最开始我们要将Tree[i]全部初始化为-1,这时的图只有一个个孤立的节点,后面读取边时再一步步更新。

为了查找节点x所在树的根节点,定义如下函数。为了在查找过程中添加路径压缩的优化(避免形成的连通子图是一个链表而不是一棵均匀的树,如果是一个链表,那么查找根节点的复杂度将上升。)

int findRoot(int x)
{
    if (Tree[x] == -1)
    {
        return x;
    }
    else
    {
        int tmp = findRoot(Tree[x]);
        Tree[x] = tmp;
        return tmp;
    }
}

上面Tree[x] = tmp就是将当前节点的双亲节点设置为查找返回的根节点编号,也可以不要这句,但为了实现上面提到的压缩路径,就加上这句,就是在往上一步步找根节点的过程中,将路上遇到的中间节点改为直接指向根节点。

以上就是并查集的基本过程。可以想到,最后,只要遍历一遍Tree[i],有多少元素等于-1,就有多少个连通子图,也就是多少个gang。但是,这题有额外的要求,也需要额外的输出。比如团伙中人员数量,团伙的老大,团伙总的通话时长等,这些不仅限制着是否为团伙,也是题目要求我们输出的信息。怎么办呢?

需要多少信息就新建多少数组,在并查集的过程中更新这些信息即可。

int Tree[2000]; // 记录每个节点的父结点

int head[2000]; // 记录每个团伙的首领,当然,我们只关心作为根节点的那些元素的值
int gangTime[2000]; // 记录每个团伙的总的通话时间,当然,我们只关心作为根节点的那些元素的值
int member[2000]; // 记录每个团伙的总人数,当然,我们只关心作为根节点的那些元素的值
int personTime[2000]; //记录每个人的总通话时间

该题并查集更新的具体方法不再赘述,请见最后的AC代码。

这里还有一个小问题就是人员的编号,因为人员是以字符串来表示的,在编程过程中很难表示和操作。可以使用C++的STL中的map将字符串和数字映射起来,为每个人员分配一个数字。

map<string, int> name2num;
map<int, string> num2name;

映射过程如下:

       // size是一个在前面定义的全局变量 
        string a, b;
        a.resize(4); // 使用scanf为string赋值前要resize
        b.resize(4);
        int t;
        scanf("%s%s%d", &a[0], &b[0], &t);
        // 更新每个人的总通话时长、更新map映射
        if (name2num.count(a) == 0) // 如果map不存在a人名,则为其分配下一个数字
        {
            name2num[a] = size;
            num2name[size] = a;
            size++;
        }
        personTime[name2num[a]] += t;
        if (name2num.count(b) == 0)
        {
            name2num[b] = size;
            num2name[size] = b;
            size++;
        }
        personTime[name2num[b]] += t;
        // 最后得到的size值就是人员的总数

贴上AC代码:

#include<stdio.h>
#include<map>
#include<string>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;

int Tree[2000];

int head[2000];
int gangTime[2000];
int member[2000];
int personTime[2000];

struct G
{
    string head;
    int member;
};
vector<G> gangs;
map<string, int> name2num;
map<int, string> num2name;

bool cmp(G G1, G G2)
{
    return (strcmp(G1.head.c_str(), G2.head.c_str()) < 0);
}

int findRoot(int x)
{
    if (Tree[x] == -1)
    {
        return x;
    }
    else
    {
        int tmp = findRoot(Tree[x]);
        Tree[x] = tmp;
        return tmp;
    }
}

int main()
{
    int size = 0;
    int N, K;
    scanf("%d%d", &N, &K);
    //init
    for (int i = 0; i < 2000; i++)
    {
        Tree[i] = -1;
    }
    for (int i = 0; i < 2000; i++)
    {
        head[i] = i;
    }
    for (int i = 0; i < 2000; i++)
    {
        member[i] = 1;
    }
    for (int i = 0; i < N; i++)
    {
        string a, b;
        a.resize(4);
        b.resize(4);
        int t;
        scanf("%s%s%d", &a[0], &b[0], &t);
        // 更新每个人的总通话时长、更新map映射
        if (name2num.count(a) == 0)
        {
            name2num[a] = size;
            num2name[size] = a;
            size++;
        }
        personTime[name2num[a]] += t;
        if (name2num.count(b) == 0)
        {
            name2num[b] = size;
            num2name[size] = b;
            size++;
        }
        personTime[name2num[b]] += t;

        int at = findRoot(name2num[a]);
        int bt = findRoot(name2num[b]);
        if (at == bt)
        {
            gangTime[at] += t;
            if (personTime[name2num[a]] > personTime[head[at]])
            {
                head[at] = name2num[a];
            }
            if(personTime[name2num[b]] > personTime[head[at]])
            {
                head[at] = name2num[b];
            }
        }
        else
        {
            Tree[at] = bt;
            member[bt] += member[at];
            head[bt] = (personTime[head[bt]] > personTime[head[at]]) ? head[bt] : head[at];
            gangTime[bt] += gangTime[at];
            gangTime[bt] += t;
            if (personTime[name2num[a]] > personTime[head[bt]])
            {
                head[bt] = name2num[a];
            }
            if (personTime[name2num[b]] > personTime[head[bt]])
            {
                head[bt] = name2num[b];
            }
        }
    }
    for (int i = 0; i < size; i++)
    {
        if (Tree[i] == -1 && member[i] > 2 && gangTime[i] > K)
        {
            G g;
            g.head = num2name[head[i]];
            g.member = member[i];
            gangs.push_back(g);
        }
    }
    sort(gangs.begin(), gangs.end(), cmp);
    printf("%d\n", gangs.size());
    for (int i = 0; i < gangs.size(); i++)
    {
        printf("%s %d\n", gangs[i].head.c_str(), gangs[i].member);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值