Head of a Gang
浙江大学 Head of a Gang
题目描述
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 threthold 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.
输入描述:
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.
输出描述:
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.
中文大意
警察找到团伙头脑的一种方法是检查人们的电话。如果A和B之间有电话,我们说A和B是相关的。关系的权重定义为两个人之间进行的所有电话呼叫的总时间长度。“帮派”是由两个以上相互关联的人组成的集群,且总关系权重大于给定的门槛K。在每个帮派中,总权重最大的是头脑。现在给出电话列表,您应该找到帮派和头脑。
对于每种情况,第一行都包含两个正数N和K(均小于或等于1000),电话数量和权重阈值。然后紧接着N行,每行都采用以下格式:
Name1 Name2 Time
其中Name1和Name2是通话两端的人员名称,而Time是通话长度。名称是从A-Z选择的三个大写字母的字符串。时间长度是一个不超过1000分钟的正整数。
对于每个测试用例,首先在一行中打印帮派总数。然后,对于每个团伙,在一行中打印头脑的名称和成员总数。可以确保每个帮派头脑都是唯一的。必须根据头脑名称的字母顺序对输出进行排序。
示例:
#输入 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 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
#输出 2 AAA 3 GGG 3 0
分析
这是一个图论问题,数据结构为带权无向图,也可以理解为带权有向图,这里以无向图理解。通过采用并查集实现集合的合并和查询,来构造这个图。使用两个结构体分别表示个人和帮派。因为本题中团伙成员的名字并不复杂(首字母不重复),所以可以用一个大小为26个元素的数组来存储结构体数组下标,这样就能根据每个人首字母的顺序使每个人与数组一一对应起来,最终通过遍历数组即可获得帮派信息,这样就不用排序,因为下标就是顺序。最终满足条件的集合的根节点所代表的人即为该集合所代表的帮派的头脑。
难点在于:成员权重增加时,可能需要更新根节点,即团伙首脑。
具体实现
#include <iostream> #include <cstring> using namespace std; const int MAXN = 26; // A-Z共26个字母 struct Person { // 个人 char name[4]; int weight; // 权重 int father; // 父节点 int number; // 记录集合元素数量,即团体成员数目,只有根节点的该字段有意义 int weight_sum; // 记录关系权重总和,只有根节点的该字段有意义 }; struct Gang { // 帮派 char head[4]; // 首领 int number; // 成员 }; Person p[MAXN]; Gang g[MAXN]; void Initial () // 初始化 { for (int i = 0; i < MAXN; i++) { Person s = {"", 0, i, 1, 0}; p[i] = s; Gang t = {"", 0}; g[i] = t; } } int Find (int x) // 查找根节点 { if (p[x].father != x) { // 根节点的父结点为自身 p[x].father = Find(p[x].father); } return p[x].father; } void Update (int x, int weight) // 更新集合 { p[x].weight += weight; while (p[x].weight > p[p[x].father].weight) { int temp = p[x].father; if (p[temp].father == temp) { // 若父节点是根节点,x成为新的根结点 p[x].father = x; // 指向父节点的指针需指向自己 p[x].number = p[temp].number; // x更新number p[x].weight_sum = p[temp].weight_sum; // x更新weight_sum,此处weight_sum并未加上weight,因为若x,y两个结点在一个集合中,可能导致x,y轮换成为根结点,加了两次weight p[temp].father = x; } else { // 若父节点不是根节点,只需更新父子关系,number和weight_sum只在根节点有意义 p[x].father = p[temp].father; p[temp].father = x; } } } void Union (int x, int y, int weight) // 合并集合 { x = Find(x); y = Find(y); if (x != y) { // 若x,y所在集合不是同一个 ,权重大的根节点作为新的根节点 if (p[x].weight < p[y].weight) { p[x].father = y; p[y].number += p[x].number; p[y].weight_sum += p[x].weight_sum + weight; } else if (p[x].weight > p[y].weight) { p[y].father = x; p[x].number += p[y].number; p[x].weight_sum += p[y].weight_sum + weight; } else { // 若两个根节点权重相同,字典排序小的作为新的根节点 if (x < y) { p[y].father = x; p[x].number += p[y].number; p[x].weight_sum += p[y].weight_sum + weight; } else { p[x].father = y; p[y].number += p[x].number; p[y].weight_sum += p[x].weight_sum + weight; } } } else { // 若x,y在一个集合,只需根节点的weight_sum加上weight p[x].weight_sum += weight; } } int main () { int N, K; while (cin >> N >> K) { char a[4], b[4]; int x, y, weight; Initial(); // 初始化 for (int i = 0; i < N; i++) { cin >> a >> b >> weight; x = int (a[0] - 'A'); // 利用名字首字母在A-Z的顺序保存到数组相应位置,即可实现排序 y = int (b[0] - 'A'); strcpy(p[x].name, a); strcpy(p[y].name, b); Update(x, weight); // 先更新 Update(y, weight); Union(x, y, weight); // 后合并 } int gangNumber = 0; for (int i = 0, j = 0; i < MAXN && j < MAXN; i++) {if (p[i].father == i && p[i].number > 2 && p[i].weight_sum > K) { // 父节点为自身是成为一个团体的首脑的首要条件 strcpy(g[j].head, p[i].name); g[j].number = p[i].number; j++; } gangNumber = j; } if (gangNumber > 0) { cout << gangNumber << endl; for (int i = 0; i < gangNumber; i++) { cout << g[i].head << ' ' << g[i].number << endl; } } else { cout << 0 << endl; } } return 0; }