1034 Head of a Gang (30分)
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.
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.
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.
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
2
AAA 3
GGG 3
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
0
给定无向带权图,求解满足条件的连通分量中 顶点权最大的顶点和该连通分量中顶点的个数。 前面这句话有点抽象。
首先这个带权的图 不仅仅是边有权值,顶点也有权值。顶点的权值就是 这个人的总通话时间。边的权值是构成边的两个顶点之间的总的通话时间。 比如 A 和B 打了一通10分钟的电话,后来呢,B和A又打了8分钟电话,再后来,A和B又打了6分钟电话。 A与C 打了4分钟电话。我们假设A和B在整个输入中,通话记录就这么多,(A与B不再与其他人通话)那么此时A的顶点权为A与B之间的24分钟加上A与C之间的4分钟,一共就是28分钟。AB之间的边权重为 AB之间的总的通话时间 也就是24分钟。
我们要求的就是在这样一幅图中,首先找出各个连通分量,然后判断
(1)这个连通分量的边权总和是不是大于给定的阈值。
(2)这个连通分量包含的节点个数是不是大于2 。大于2才有资格称为一个小团伙。
首先要解决的一个问题就是 输入给的是人的名字,不是我们常见的 数字。因此我们可以把名字映射成数字。按照谁先出现,谁先编号的原则。比如第一行输入是AAA BBB 那么给AAA 编号为0 , BBB 编号为1。 具体的做法是统计当前出现了多少人(不重复,之前出现的就已经编过号,直接把它的号取出来),比如当前出现的人是第一个人,那么总人数为1 , 我们把第一个人编号为0 (1-1) ,如果紧接着又出现了一个人,总人数为2 , 第二个人我们编号为1(2-1) 也是说如果总人数为N,我们编号的范围是 【0…N-1】 。当然这里只是一个编号的习惯问题,你也可以编号为【1…N】 .编号后,就可以将字符串映射到数组下标,然后进行图的存储。
由于给定N条通话记录,如果通话的人全部都不同,最多会有2*N个不同的人,因此,矩阵需要开到 2N * 2N 。
我们使用treeMap 一个好处是,当出现多个团伙符合要求,需要按照 字典序排序,基于红黑树的TreeMap 会自动对输入的
我们在深度优先遍历时,每次访问过一条边之后,把这条边删掉,这样可以避免不小心重复计算。
如何您还有其他疑问或者思路,欢迎在留言区留言或者私信与我讨论交流。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;
import java.util.TreeMap;
// 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
public class P1034 {
static int recordNum, k; // 通话记录数 和 阈值
static int totalNum = 0; // 总人数
static int[][] graph; // 存储整个图
static int[] weight; // 个人通话时长
static boolean[] visited; // 访问标记数组
static int headId, numMember, totalweight; // 头目的ID、小团伙的人数、小团伙的总通话时间
static Map<String, Integer> StringtoNum = new TreeMap<>(); // 把名字转换成ID
static Map<Integer, String> NumtoString = new TreeMap<>(); // 把ID转换成名字
static Map<String, Integer> head = new TreeMap<>(); // 小团伙的头目和人数
public static void main(String[] args) throws IOException {
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
String[] info = bf.readLine().split(" ");
recordNum = Integer.parseInt(info[0]); // 通话记录条数
k = Integer.parseInt(info[1]); // 连通分量权重阈值
graph = new int[recordNum * 2][recordNum * 2]; // 图的初始化
weight = new int[recordNum * 2]; //顶点权数组
visited = new boolean[recordNum * 2]; // 访问标记数组
for (int i = 0; i < recordNum; i++) {
// 填充整个图
String[] rec = bf.readLine().split(" ");
int id1 = change(rec[0]); // 将名字转换成数字ID
int id2 = change(rec[1]);
int time = Integer.parseInt(rec[2]);
weight[id1] += time;
weight[id2] += time;
graph[id1][id2] += time;
graph[id2][id1] += time;
}
for (int j = 0; j < totalNum; j++) {
if (!visited[j]) {
headId = j; // 头目暂定为j
numMember = 0; // 团伙的成员数
totalweight = 0; // 团伙的总通话时间
// 深度优先遍历从j开始
DFS(j);
if (numMember > 2 && totalweight > k) {
head.put(NumtoString.get(headId), numMember);
}
}
}
System.out.println(head.size());
for (String name : head.keySet()) {
System.out.printf("%s %d\n", name, head.get(name));
}
}
static void DFS(int start) {
numMember++; // 团伙成员+1
visited[start] = true; // start 这个成员标记为访问过
if (weight[start] > weight[headId])
headId = start;
for (int i = 0; i < totalNum; i++) {
// 遍历所有人
if (graph[start][i] > 0) {
totalweight += graph[start][i];
graph[start][i] = 0; // 删掉这条边
graph[i][start] = 0;
if (!visited[i]) {
// 以i为起点再次进行深度优先遍历
DFS(i);
}
}
}
}
// 给输入的名字进行编号,采用先来先编号的做法。编号范围[0 .....totalNum-1]
static int change(String name) {
if (!StringtoNum.containsKey(name)) {
// 如果之前没有出现过 我们按照人头顺序编号
StringtoNum.put(name, totalNum++);
NumtoString.put(totalNum - 1, name);
return totalNum - 1;
} else
return StringtoNum.get(name); // 如果出现过,返回ID即可
}
}