hihocoder week171分析---并查集

Hihocoder Week171 题目解析

题目

描述
You are given a list of usernames and their email addresses in the following format:

alice 2 alice@hihocoder.com alice@gmail.com
bob 1 bob@qq.com
alicebest 2 alice@gmail.com alice@qq.com
alice2016 1 alice@qq.com

Your task is to merge the usernames if they share common email address:

alice alicebest alice2016
bob

输入
The first line contains an integer N, denoting the number of usernames. (1 < N ≤ 10000)

The following N lines contain N usernames and their emails in the previous mentioned format.

Each username may have 10 emails at most.

输出
Output one merged group per line.

In each group output the usernames in the same order as the input.

Output the groups in the same order as their first usernames appear in the input.

样例输入
4
alice 2 alice@hihocoder.com alice@gmail.com
bob 1 bob@qq.com
alicebest 2 alice@gmail.com alice@qq.com
alice2016 1 alice@qq.com

样例输出
alice alicebest alice2016
bob

题目分析

拿到题目之后,首先分析能否使用暴力的查找得到结果。但是,马上被否定掉了,如果所有的用户的email都不相同的话,我们需要每处理一个用户,都需要和前面所有的用户进行比较,空间复杂度为 O(N^2),可以放弃了。

所以我们需要考虑一个更加高效的方法。我们想到了并查集。

并查集的主要用处是对一个集合进行高效的分类。(通过合并操作)我们的思路就来了,如果两个用户有一个相同的email,那么就可以将它们合并。

继续分析,如何知道两个用户是否有一个相同的email呢? 我们可以直接将所有的可能email取出来,之后建立email到对应所有用户的映射。步骤如下
1. 读取一个用户的所有email,
2. 对每一email,在之前出现过的集合中进行搜索(O(log(10N))),如果有的话,将其添加到对应的用户集合中;否则新建一个email和其对应的用户集合。

上述操作的复杂度为O(N*10*log(10N)),可以接受。

之后对于每一个email何其对应的用户集合T,我们对并查集{0,1,…,n-1}(所有用户)进行并查集合并。这里我们将T中的所有用户合并到第一个用户上。最多进行O(10N)次合并。合并的复杂度比较低O(1)。

最后我们将所有用户对应的组统计出来,使用并查集个get_parent()方法。复杂度O(N)。之后按序输出。(自己想点技巧吧)

代码:

#include <iostream>
#include <vector>
#include <string>
#include <map>
#include <cstring>
#include <utility>
#include <set>

using namespace std;

int n;

int mset[10010];//集合index的类别,或者用parent表示
int mrank[10010];//集合index的层次,通常初始化为0

bool reach[10010];
int group[10010];

//初始化集合
void Make_Set(int i)
{
    mset[i]=i;//初始化的时候,一个集合的parent都是这个集合自己的标号。没有跟它同类的集合,那么这个集合的源头只能是自己了。
    mrank[i]=0;
}

int Find_Set(int i)
{ 
    //如果集合i的父亲是自己,说明自己就是源头,返回自己的标号
   if(mset[i]==i)
       return mset[i];
    //否则查找集合i的父亲的源头
    return  Find_Set(mset[i]);        
}

void Union(int i,int j)
{
    i=Find_Set(i);
    j=Find_Set(j);
    if(i==j) return ;
    if(mrank[i]>mrank[j]) mset[j]=i;
    else
    {
        if(mrank[i]==mrank[j]) mrank[j]++;   
        mset[i]=j;
    }
}

// 沿路标记,减少查询
int find_fa(int i){
    if(mset[i] == i){
        reach[i] = true;
        group[i] = i;
        return i;
    }else{
        group[i] = find_fa(mset[i]);
        reach[i] = true;
    }
}

vector<int> emailMp[100010];
map<string, int> mp;
string allName[10010];

int main(int argc, char const *argv[])
{
#ifndef ONLINE_JUDGE
    freopen("in", "r", stdin);
#endif

    mp.clear();
    cin >> n;

    int email_cnt = 0;

    for(int i = 0; i < n; i++){
        string name_str;
        int cnt;
        cin >> name_str;
        allName[i] = name_str;
        mp[name_str] = i;

        cin >> cnt;
        for(int j = 0; j < cnt; j++){
            string ename;
            cin >> ename;

            if(mp.find(ename) == mp.end()){
                // no previous email
                mp[ename] = email_cnt;
                emailMp[email_cnt].push_back(i);
                email_cnt++;
            }else{
                // have previous email
                int eid = mp[ename];
                emailMp[eid].push_back(i);
            }
        }
    }

    // init union find
    for(int i = 0; i < n; i++){
        Make_Set(i);
    }

    // union 
    for(int i = 0; i < email_cnt; i++){
        int head = emailMp[i][0];
        for(int j = 1; j < emailMp[i].size(); j++){
            Union(head, emailMp[i][j]);
        }
    }

    // get each user's group
    memset(reach, 0, sizeof(reach));

    for(int i = 0; i < n; i++){
        if(reach[i] == false){
            find_fa(i);
        }
    }

    // get output
    map<int, vector<int> > divide;

    for(int i = 0; i < n; i++){
        if(divide.find(group[i]) == divide.end()){
            vector<int> new_divide;
            new_divide.push_back(i);
            divide.insert(make_pair(group[i], new_divide));
        }else{
            divide[group[i]].push_back(i);
        }
    }

    set<int> has_appear;

    for(int i = 0; i < n; i++){
        if(has_appear.find(group[i]) == has_appear.end()){
            has_appear.insert(group[i]);
            std::vector<int> v = divide[group[i]];
            for(int j = 0; j < v.size(); j++){
                cout << allName[v[j]] << " ";
            }
            cout << endl;
        }
    }



    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值