PAT 甲级 1034 Head of a Gang

语雀满了,只能暂时先用CSDN来记录一下了。

这道题之前看王道书的时候做过一次,当时是照着查并集的思想做的。昨天刷PAT重做的时候,第一时间想到的还是查并集,然后尝试着自己独立去写,发现仅仅用查并集其实还是有很多的坑的。

Sample Input 1:
8 59
AAA BBB 10
BBB AAA 20
AAA CCC 40
DDD EEE 5
EEE DDD 70
FFF GGG 30
GGG HHH 20
HHH FFF 10
Sample Output 1:
2
AAA 3
GGG 3

Sample Input 2:
8 70
AAA BBB 10
BBB AAA 20
AAA CCC 40
DDD EEE 5
EEE DDD 70
FFF GGG 30
GGG HHH 20
HHH FFF 10
Sample Output 2:
0

 题目大意

给定N条通话记录和一个限制K。每条通话记录包含了两个名字和一个通话时间,相互联系的人同属于一个帮派。题目要求,只有人数超过2人并且总通话时长超过K的集体才能算作帮派Gang,并且帮派内部通话时长最长的人是帮派首领Boss。

解法一(繁琐)

我看到需要区分集合,所以第一时间就想到了查并集。于是就有了以下思路:

  1. 创建一个map,用来完成人名和id编号的映射。使用number来记录人数,如果没有当前人名才++。
  2. 创建一个person结构,存储每个人的名字和总通话时间,下标是映射的id,使用people数组进行保存。
  3. 编写Find查找和Union合并的函数,来对每个人的集合进行划分。(Find函数一定要带路径压缩
  4. 创建一个set容器(可以忽略重复项),用来保存找到的leader,顺带计算每个帮派的总通话时间,保存在total里,以leader的id为下标(不一定是真的leader)。然后,再把所有相关的人保存到一个邻接表gang里面(方便统计每个帮派的人数)。
  5. 然后就是根据gang对应的帮派人数和总通话时间,来判断是否为帮派。如果是,把leader的id转储到向量里面(本来想用set.erase,但是不知道为什么会出bug...)
  6. 最后就是根据每个人的通话时间,找到已知帮派里面的真正的leader,保存到优先队列里面,有序输出。

虽然这样也能实现,但是过程很繁琐,而且很容易出现bug。

  1. 查并集的生成的树的根节点不是根据total来算的,所以根节点不一定是真正的leader
  2. 虽然Find函数能够实现路径压缩。但是当根结点又与另一个集合合并时候,孩子的father仍然指向旧根节点,而不是合并后的新根结点。所以在后面遍历people数组时候,必须要对每一个结点都要再Find一次,再一次路径压缩。
  3. 结果必须是按照string有序输出。
  4. 通话记录最多有1000条,所以数组必须要≥2000。
#include <bits/stdc++.h>
using namespace std;

const int maxn=2020;
struct person
{
    string name;
    int t=0;
} people[maxn];

map<string,int> id;
map<int,int>total;
vector<person> gang[maxn];
int father[maxn];
int height[maxn];

void Init()
{
    for(int i=1; i<=maxn; ++i)
    {
        father[i]=i;
        height[i]=0;
    }
    return ;
}

int Find(int x)
{
    if(x!=father[x])
    {
        father[x]=Find(father[x]);
    }
    return father[x];
}

void Union(int a,int b)
{
    int xa=Find(a);
    int xb=Find(b);
    if(xa!=xb)
    {
        if(height[xa]<height[xb])
        {
            father[xa]=xb;
        }
        else if(height[xa]>height[xb])
        {
            father[xb]=xa;
        }
        else
        {
            father[xb]=xa;
            height[xa]++;
        }
    }
}
int cmp(person a,person b)
{
    return a.t>b.t;
}
int main()
{
    int n,k,number=1;
    while(cin >> n >>k)
    {
        number=1;
        Init();
        for(int i=0; i<n; ++i)
        {
            string a,b;
            int t;
            cin >> a>> b>> t;
            if(id[a]==0)
            {
                id[a]=number++;
                people[id[a]].name=a;
            }
            if(id[b]==0)
            {
                id[b]=number++;
                people[id[b]].name=b;
            }
            people[id[a]].t+=t;
            people[id[b]].t+=t;
            Union(id[a],id[b]);
        }
        int cnt=0;
        set<int> leaders;
        for(int i=1; i<number; ++i)
        {
            Find(i);
            gang[father[i]].push_back(people[i]);
            total[father[i]]+=people[i].t;
            leaders.insert(father[i]);
        }
        set<int>::iterator it=leaders.begin();
        vector<int> ans;
        for(it=leaders.begin(); it!=leaders.end(); ++it)
        {
            if(total[(*it)]>k*2 && gang[(*it)].size()>2)
            {
                ans.push_back((*it));
                cnt++;
            }
        }
        cout <<cnt<<"\n";
        map<string,int> output;
        for(int i=0; i<cnt; ++i)
        {
            int key=ans[i],tmax=0;
            string boss;
            for(int i=0; i<gang[key].size(); ++i)
            {
                if(gang[key][i].t>tmax)
                {
                    tmax=gang[key][i].t;
                    boss=gang[key][i].name;
                }
            }
            output[boss]=gang[key].size();
        }
        map<string,int>::iterator ot=output.begin();
        for(;ot!=output.end();++ot){
            cout << ot->first<<" "<<ot->second<<"\n";
        }
    }
    return 0;
}

 解法二

王道书里提供的解法,是查并集的变体。

使用了map<string,string> 来保存father,这样就省去了id之间映射。

同样需要考虑到并查集的根节点不一定是真正首领的问题

#include <bits/stdc++.h>

using namespace std;
map<string, string>father;
map<string, int>height;
map<string, int>weight;
map<string, int>gangnum;
priority_queue<string, vector<string>, greater<string> >gang;
vector<string>head;   //保存并查集的根节点
vector<string>head2;  //找到真正的头领

string Find(string name) {
    //如果能找到,就一直找到根节点
    if (father.find(name) != father.end()) {
        if (name != father[name]) {
            father[name] = Find(father[name]);
        }
    } else {
    //如果找不到,就初始化自己
        father[name] = name;
        height[name] = 0;
    }
    return father[name];
}


void Union(string a, string b, int t) {
    //将查并的标准改成weight,weight更大的成为父节点
    weight[a] += t;
    weight[b] += t;
    a = Find(a);
    b = Find(b);
    if (a != b) {
        if (weight[a] < weight[b]) {
            father[a] = b;
        } else if (weight[a] > weight[b]) {
            father[b] = a;
        } else {
            father[b] = a;
            height[a]++;
        }
    }
}

void FindHead() {
    map<string, string>::iterator it;
    for (it = father.begin(); it != father.end(); ++it) {
        if (it->first == it->second) {
            head.push_back(it->first);
        }
    }
}

void FindReal() {
    for (int i = 0; i < head.size(); ++i) {
        map<string, string>::iterator it;
        int maxn = 0;
        string name;
        for (it = father.begin(); it != father.end(); ++it) {
            //因为查并集的根节点不一定是真的头领
            //所以需要找到权重最大的人,他才是头领
            if (head[i] == Find(it->first)) {
                //cout << it->first << '\n';
                if (weight[it->first] > maxn) {
                    maxn = weight[it->first];
                    name = it->first;
                }
            }
        }
        head2.push_back(name);
    }
    //printf("M:%d\n",weight["M"]);
    //printf("Q:%d\n",weight["Q"]);
    //printf("S:%d\n",weight["S"]);
}

void FindGang(int k) {
    FindHead();
    FindReal();
    //根据并查集的根节点,计算每个帮派的总时长
    for (int i = 0; i < head.size(); ++i) {
        //cout << head2[i]<<'\n';
        //cout << k << '\n';
        string leader = head[i];
        int num = 0, sum_weight = 0;
        map<string, string>::iterator it;
        for (it = father.begin(); it != father.end(); ++it) {
            if (it->second == leader) {
                sum_weight += weight[it->first];
                num++;
            }
        }
        if (num > 2 && sum_weight > 2 * k) {
            gang.push(head2[i]);
            gangnum[head2[i]] = num;
        }
    }
}


void initial() {
    father.clear();
    height.clear();
    weight.clear();
    gangnum.clear();
    head.clear();
}

int main() {
    int n, k;
    while (scanf("%d%d", &n, &k) != EOF) {
        int time = 0;
        while (n--) {
            string str1;
            string str2;
            int t;
            cin >> str1 >> str2 >> t;
            Union(str1, str2, t);
        }
        FindGang(k);
        cout << gang.size() << '\n';
        while (!gang.empty()) {
            cout << gang.top() << ' ' << gangnum[gang.top()] << '\n';
            gang.pop();
        }
        initial();
    }
    return 0;
}

解法三

晴神书里的解法,用DFS来做。

  1. 通过两个map,来完成name和id的互相映射,方便哈希查找。
  2. 再用一个map,来保存头领姓名和帮派人数的,保存答案。
  3. w[]数组来保存每个人的通话时间,g[][]来保存边的权重,并且方便dfs找到帮派
  4. 找到新的w更大的结点,head指向该结点,成为新的头领。
  5. totalweight来记录每个帮派的总通话时间。一定要记住,先遍历边,累加了通话时间之后一定要把这条边删除。这样一方面避免出现的情况,造成边权重漏加或者重复加的情况。
#include <bits/stdc++.h>
using namespace std;

const int maxn=2010;
const int inf=1e8;

map<string,int> nameToid;//姓名转编号
map<int,string> idToname;//编号转姓名
map<string,int> gang;    //头领->人数
int n,k,number=0;        //通话记录数量,最低时间,总人数
int w[maxn]={0},G[maxn][maxn]={0};
bool vis[maxn]={false};

//遍历节点
void DFS(int now,int &head,int &gangnum,int &totalweight){
    gangnum++;     //成员数量增加
    vis[now]=true; //已经访问过当前结点
    if(w[now]>w[head]){
        head=now;    //找到权重最大的点,成为新的首领
    }
    for(int i=0;i<number;++i){
        if(G[now][i]>0){
            totalweight+=G[now][i];  //累加这条边
            G[now][i]=G[i][now]=0;   //将这两个点之间的边删除
            if(!vis[i]){ //如果下一个点还没被访问过,就访问
                DFS(i,head,gangnum,totalweight);
            }
        }
    }
}

//遍历整个图
void DFSTrave(){
    for(int i=0;i<number;++i){
        if(!vis[i]){
            int head=i,gangnum=0,totalweight=0; //不要忘了初始化
            DFS(i,head,gangnum,totalweight);
            if(gangnum>2 && totalweight>k){
                    //gang是保存name对应人数的
                gang[idToname[head]]=gangnum;
            }
        }
    }
}
//完成名字和id的编号,方便查找
int change(string s){
    //找到的话,就不需要再添加姓名了
    if(nameToid.find(s)!=nameToid.end()){
        return nameToid[s];
    }else{
        nameToid[s]=number;
        idToname[number]=s;
        return number++;
    }
}
int main(){
    int weight;
    cin >> n>>k;
    string s1,s2;
    for(int i=0;i<n;++i){
        cin >> s1>>s2 >>weight;
        int id1=change(s1);
        int id2=change(s2);
        w[id1]+=weight;         //保存到对应的权重里
        w[id2]+=weight;
        G[id1][id2]+=weight;    //保存到邻接矩阵里面
        G[id2][id1]+=weight;
    }
    DFSTrave();
    cout << gang.size()<<"\n";
    //因为map是按照关键字从小到大输出的,所以不用排序
    map<string,int>::iterator it;
    for(it=gang.begin();it!=gang.end();++it){
        cout << it->first << " "<< it->second<<"\n";
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值