1034. Head of a Gang (30)

1034. Head of a Gang (30)

1. 题目:

警方通过核对人们的通话记录查找团伙的头目。如果两个人有一条通话记录,那么称这两个人有关系。两个人关系的权重用他们之间通话记录的总长度表示。如果一个群体超过2个人,相互有联系,而且群体总权重超过阈值K,那么把这个群体叫做gang。在gang中,权重最高的人叫做head。现在给定N条通话记录,以及阈值K,找出所有gang的head,以及gang中团体的个数。

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

2. 思路:

观察例子中FGH之间的关系,这个团体中G是head,说明权值是双向的,如果A和B产生了长度是w的通话,那么A和B的权值都要+w。
题目转换成:给定一张无向图,找出所有(权值总数超过阈值K,节点>2)的连通子图,找出连通子图中度最大的结点。
不带回溯的DFS访问连通子图的所有结点,求和得到连通子图的权值总和,比较得出权值最大的结点,求出结点总数。
难点在于:

2.1 结点的表示与图的存储:

题目给出的结点都是长度为3位的[A-Z]的字母组合。存储起来, 要把字符串映射成数字。我的做法是做一个map<string, int>记录string结点和对应的int下标,使得每个string类型的结点都有一个int下标与之对应。每次遇到一个结点,先判断该节点(key)是否在map中出现过,如果出现过,直接读取value;如果没出现过,向map中加入该节点。

2.2 如何在读取边的时候记录所有结点的权值:

刚开始我的想法是结点用(id, weigh)表示,对于每个结点,只记录与它有边相连的结点。比如,读取到边A B 20:把(B,20)加到结点A后面,把(A, 20)加到结点B后面。这样有个问题,下次来了边B A 10,我们需要先判断A后面有没有B,有则更新,无则插入。这样在构建图的时候需要每次来了一条边都要对两个结点的连接节点做遍历。最坏情况下时间复杂度O(N^2),N表示结点数。(所有的其他结点都和某个结点相连)有超时的可能。所以,我改进了方法,把联通关系和权值分开存放,

vector< vector<int> > map1(2020, vector<int>());   //记录连通关系
vector<int> weigh(2020);   //记录每个结点的权重

这样来了边A B 20,我们执行:

weigh[A] += 20;
weigh[B] += 20
map1[A].push_back(B)
map1[B].push_back(A)

来了边B A 10,我们执行:

weigh[A] += 10;
weigh[B] += 10
map1[A].push_back(B)
map1[B].push_back(A)

这样造成的结果是结点A后面有两个B。最差情况下所有结点后面每个结点都出现两次,访问时间上是线性增长的是可以容忍的。DFS的时候,做的是不带回溯的DFS,所以访问A结点的时候,遍历与A结点连接的所有结点,访问完第一个B,visit[B]=true. 再遇到第二个B,不会重复访问。所以这种每个结点出现两次的情况对不带回溯的DFS是不影响的。but如果做得是带回溯的DFS,
A: B, C, B
B: F
C: G
那么访问A的时候,A->B->F访问完,回溯到A,访问A->C->G,回溯到A,又会访问一次A->B->F。造成重复访问。

2.3 求出联通子图中所有边的权值之和:

刚开始我的想法是(妄图)用带回溯的DFS遍历图中所有边,通过调试我发现我的想法很naive。题目中FHG相互联通
F -> G -> H
F -> H
DFS过程中,当FGH这条线结束后,回溯到F点,走FH这条线。走到H的时候,发现G没走过,又把HG这条线走了一遍,这样导致GH被重复走了一次!这样计算出的总权值多计算了一次GH边的权值。
正确的食用方法是:记录图中每个结点的权值,用不带回溯的DFS访问图中所有点,求所有结点的权值之和。由于无向图中一条边的权值同时加在了两个结点上,导致最终计算出的所有结点的权值是图中所有边权的二倍。(除以2就好了嘛)

2.4 隐藏BUG:段错误。

给定图中的N条边,那么图中最多有多少个结点?最开始,我认为结点两两相连的话,是N+1个结点。but N条边最多能构成2N个结点,就是每个子图只有两个结点的情况!挑bug挑的心累,sigh……

3. 代码

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

int e, k;   //e边数
int n = 0;   //n节点数
vector< vector<int> > map1(2020, vector<int>());
vector<int> weigh(2020);   //记录每个结点的权重

bool visit[2020];
int maxWeigh_id;   //dfs记录联通子图中最大权重的结点
int node;   //dfs记录连通子图的结点个数
int degrees;   //dfs记录连通子图所有结点的度数之和
void DFS(int cur) {
    for(int i = 0; i < map1[cur].size(); i++) {
        if(!visit[map1[cur][i]]) {
            visit[map1[cur][i]] = true;
            node++;
            degrees += weigh[map1[cur][i]];
            if(weigh[map1[cur][i]] > weigh[maxWeigh_id]) {
                maxWeigh_id = map1[cur][i];
            }
            DFS(map1[cur][i]);
        }
    }
}

typedef struct res {
    string sid;
    int cnt;
    res(string s="", int c=0) {
        sid = s;
        cnt = c;
    }
    bool operator<(const res &other)const {
        return sid < other.sid;
    }
}Res;
vector<Res> re;

int main() {
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);

    scanf("%d%d", &e, &k);
    map<string, int> id;
    map<int, string> id_r;
    for(int i = 0; i <= e - 1; i++) {
        string s1, s2;      int w;  
        cin >> s1 >> s2;    scanf("%d", &w);
        if(id.count(s1) == 0) {
            id[s1] = n++;
            id_r[id[s1]] = s1;
        }
        if(id.count(s2) == 0) {
            id[s2] = n++;
            id_r[id[s2]] = s2;
        }
        map1[id[s1]].push_back(id[s2]);
        map1[id[s2]].push_back(id[s1]);
        weigh[id[s1]] += w;
        weigh[id[s2]] += w;
    }

    memset(visit, 0, sizeof(visit));
    for(int i = 0; i <= n - 1; i++) {
        if(!visit[i]) {
            visit[i] = true;
            maxWeigh_id = i;
            node = 1;
            degrees = weigh[i];
            DFS(i);

            if(degrees/2 > k && node > 2) {
                int id = maxWeigh_id;
                string sid = id_r[id];
                Res tmp(sid, node);
                re.push_back(tmp);
            }
        }
    }
    if(re.size() == 0) {
        printf("0");
        return 0;
    }
    sort(re.begin(), re.end());
    printf("%d\n", re.size());
    for(int i = 0; i < re.size(); i++) {
        cout << re[i].sid << " " << re[i].cnt << endl;
    }
    return 0;
}

4. 另一种思路

参考sunbaigui的解法,自己写了个并查集的版本,感觉更简洁一点(思路是最重要的嗯,每次做不出来的题,百度一下,第一条都是suibaigui的解法,膜拜~)

4.1思路:

并查集记录结点之间的连通关系。(并查集其实就是记录每个节点的父亲)。

typedef struct result {
    int nodes;   //结点总数
    int weigh;   //总权重
    int head;   //首领(权重最大的结点)
    bool operator<(const result &other)const {
        return rname[head] < rname[other.head];
    }
}Result;

map<int, Result> res;

上面的res的key记录连通子图的根结点,value记录连通子图的(结点总数,总权重,首领)。在遍历并查集的时候,每次找到当前结点的根节点,如果当前根节点已经出现在res中,则更新它的calue;否则,新建新节点。

vector<Result> re;

得到res后,过滤出(结点个数>2 && 总权值>K)的key,把value记录到re中。输出结果即可。

4.2 一个易错点

建立好并查集后,每次查找结点i的root,不能直接用root[i],而是Find(i)。

4.3 代码
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<map>
#include<string>
#include<math.h>
#include<vector>
#include<algorithm>
using namespace std;
#define MAX_N 2020
map<string, int> name;
map<int, string> rname;
vector<int> weigh(MAX_N);

vector<int> root(MAX_N);
int Find(int a) {
    int x = a;
    while(x != root[x]) {
        x = root[x];
    }
    //压缩路径
    int y = a;
    while(y != x) {
        int t = root[y];
        root[y] = x;
        y = t;
    }
    return x;
}

void Union(int a, int b) {
    int x = Find(a);
    int y = Find(b);
    if(x > y) {
        root[x] = y;
    }
    else if(x < y) {
        root[y] = x;
    }
}

typedef struct result {
    int nodes;   //结点总数
    int weigh;   //总权重
    int head;   //首领(权重最大的结点)
    bool operator<(const result &other)const {
        return rname[head] < rname[other.head];
    }
}Result;

map<int, Result> res;
vector<Result> re;
int main() {
    //freopen("in.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    int E, K;
    int n = 0;   //结点总数
    scanf("%d%d", &E, &K);
    for(int i = 0; i < MAX_N; i++) {
        root[i] = i;
    }
    for(int i = 0; i <= E - 1; i++) {
        string s1, s2;
        int w;
        cin >> s1 >> s2;
        scanf("%d", &w);
        if(name.count(s1) == 0) {
            name[s1] = n++;
            rname[name[s1]] = s1;
        }
        if(name.count(s2) == 0) {
            name[s2] = n++;
            rname[name[s2]] = s2;
        }
        weigh[name[s1]] += w;
        weigh[name[s2]] += w;
        Union(name[s1], name[s2]);
    }

    for(int i = 0; i < n; i++) {
        int id = Find(i);
        if(res.count(id) == 0) {
            Result r;
            r.head = i;
            r.nodes = 1;
            r.weigh = weigh[i];
            res[id] = r;
        }
        else {
            res[id].nodes += 1;
            res[id].weigh += weigh[i];
            if(weigh[i] > weigh[res[id].head]) {
                res[id].head = i;
            }
        }
    }

    for(map<int, Result>::iterator it = res.begin(); it != res.end(); it++) {
        if(it->second.nodes > 2 && (it->second.weigh)/2 > K) {
            re.push_back(it->second);
        }
    }
    if(re.empty()) {
        printf("0");
    }
    else {
        sort(re.begin(), re.end());
        cout << re.size() << endl;
        for(int i = 0; i < re.size(); i++) {
            cout << rname[re[i].head] << " " << re[i].nodes << endl;
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值