PAT 1034 Head of a Gang (30分) 图的连通分量 + DFS

Scroll Down

题目

One way that the police finds the head of a gang is to check people's phone calls. If there is a phone call between A and B, we say that A and B is related. The weight of a relation is defined to be the total time length of all the phone calls made between the two persons. A "Gang" is a cluster of more than 2 persons who are related to each other with total relation weight being greater than a given threshold K. In each gang, the one with maximum total weight is the head. Now given a list of phone calls, you are supposed to find the gangs and the heads.

Input Specification:
Each input file contains one test case. For each case, the first line contains two positive numbers N and K (both less than or equal to 1000), the number of phone calls and the weight threthold, respectively. Then N lines follow, each in the following format:

Name1 Name2 Time

where Name1 and Name2 are the names of people at the two ends of the call, and Time is the length of the call. A name is a string of three capital letters chosen from A-Z. A time length is a positive integer which is no more than 1000 minutes.

Output Specification:
For each test case, first print in a line the total number of gangs. Then for each gang, print in a line the name of the head and the total number of the members. It is guaranteed that the head is unique for each gang. The output must be sorted according to the alphabetical order of the names of the heads.

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

题目解读

警方通过核对人们的通话记录查找团伙的头目。如果两个人有一条通话记录,那么称这两个人有关系。两个人关系的权重用他们之间通话记录的总时长表示。如果一个群体超过2个人相互有联系,而且群体总权重(总通话时长)超过阈值K,那么这个群体就是一个团伙gang,其中权重最高的人叫做head

现在给定N条通话记录,以及阈值K,输出所有满足条件的团伙的头目head,和他们团伙里面的人数。

思路分析

刚开始的时候我陷入了一个误区,就是我以为群体里面必须两两之间都有联系,就是说如果A和B有通话记录,B和C有通话记录,但是A和C没有通话记录,那么这就不是一个团伙。然后这样分析下来好像十分困难,我就找了找别人的博客,发现我的思维是错误的,这就是最基本的图的连通分量的问题,用深度优先遍历就可以了。

  • 因为我们构建图都是根据数字用的编号,所以需要把字母映射为id,存储在两个map里面,一个字符串对应id,一个id对应字符串,maxid保存一共有几个人(节点)。最后用一个map<string, int>保存每个团伙的<头目,人数>
  • 建立无向图,我这里用的邻接表vector<vector<int>>),当然也可以用二维数组。用二位数组能够同时保存每条边的权重,邻接表只能表示出节点之间的连接关系。但其实边的权重不用保存
  • 我们需要判断的是群体的权重要大于K,头目的权重要最大,看起来我们应该保存点的权重,边的权重,但其实保存了点的权重就不用保存边的权重了。
    • 对于一个无向图的连通分量来说,所有边的权重之和=所有顶点的权重之和 / 2。既然存储顶点的权重就可以,那我就没必要再去存边的权重。
    • 如果按边的权重和去计算团伙(连通分量)的总权重,那么每次把一条边的权重统计进去之后还要把它变为0,以免重复计算(比如以u出发去找连通分量中的点,找到v就把g[v][u]算进去,v的邻接点又会找到u,又会把g[u][v])就造成重复计算。
    • 按照顶点的权重和计算就不用考虑上面的问题,我只需要把连通分量中所有顶点的权重和加起来最后除以2就可以。
  • 主函数中,以每个顶点开始去找他所属的连通分量(dfs),并在dfs的过程中得到这这个连通分量的顶点数目、权重最大的顶点(头目)、全部顶点的权重和(团伙的权重)。然后判断 数量>2 && 权重和 / 2 > K。添加进结果集map。

代码

#include <iostream>
#include <map>
#include <vector>
using namespace std;

// N个通话记录,K是阈值
int N, K;
// 每个点的权重
vector<int> point_weight;
// 每条边的权重
vector<vector<int>> edge;

// 一共几个人,编号从0开始
int maxid;

// 映射表
map<string, int> str_to_int;
map<int, string> int_to_str;

// 结果,自动排序
map<string, int> gang;

// 用于dfs过程记录节点是否已被访问
vector<bool> visited;

// 字符串转编号
int ConvertStrToId(string name) {
    // 映射已存在
    if (str_to_int.find(name) != str_to_int.end()) {
        return str_to_int[name];
    } else {
        // 添加映射
        str_to_int[name] = maxid;
        int_to_str[maxid] = name;
        // 人数id+1
        return maxid++;
    }
}

// 每次dfs可找出一个连通分量,也就是一个团伙,并能计算出团伙人数、权、和头目
// 注意引用传值
void dfs(int v, int &head, int &member, int &weight) {
    // 当前节点标记为已访问
    visited[v] = true;
    // 连通分量节点数增加
    member++;
    // 所有节点权重和
    weight += point_weight[v];
    // 权重最大的顶点是头目
    if (point_weight[v] > point_weight[head])
        head = v;
    // 以这个点出发找他的所有未访问的邻接点
    for (int j: edge[v]) {
        if (!visited[j])
            dfs(j, head, member, weight);
    }
}

int main() {
    cin >> N >> K;
    // N个记录,最多有2N个人
    point_weight.resize(2 * N);
    edge.resize(2 * N);
    visited.resize(2 * N);

    // 输入
    string name1, name2;
    int w;
    for (int i = 0; i < N; ++i) {
        cin >> name1 >> name2 >> w;
        int id1 = ConvertStrToId(name1), id2 = ConvertStrToId(name2);
        // 构建邻接表
        edge[id1].push_back(id2);
        edge[id2].push_back(id1);
        // 增加点的权值
        point_weight[id1] += w;
        point_weight[id2] += w;
    }
    // 每次dfs找到一个连通分量
    for (int i = 0; i < maxid; ++i) {
        if (!visited[i]) {
            int head = i, member = 0, weight = 0;
            dfs(i, head, member, weight);
            // 判断这个连通分量是否形成一个团伙
            if (member > 2 && weight / 2 > K) {
                // 加入结果集
                gang[int_to_str[head]] = member;
            }
        }
    }
    // 输出几个团伙
    cout << gang.size() << endl;
    // 输出每个团伙的头目和人数
    for (auto it = gang.begin(); it != gang.end(); ++it)
        cout << it->first << " " << it->second << endl;

    return 0;
}