PATAdvanceLevel1034

题目链接:1034 Head of a Gang (30 分)

题目描述如下:

作者: CHEN, Yue
单位: 浙江大学
时间限制: 400 ms
内存限制: 64 MB
代码长度限制: 16 KB
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 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.

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

题目大意:

(英语能力有限,有些翻译可能不是特别准确)
警方找到一个团伙头目的一个方法是检测人们的通话纪录。如果A和B之间有通话纪录我们就说A和B有关系。一个关系的权重定义为这两人之间总的通话时长。一个团伙是他们之间的关系总权重(即总的通话时长)大于给定的K且人数大于2的一群人。每一个团伙中总权重最大(即通话时长最长)的那个人就是头目。现在给出一个通话列表,要求你找出其中的团伙和头目们。
输入
每一个输入文件包含一个测试样例。每一个测试样例的第一行有两个正数N和K(都小于等于1000),分别代表通话数和给定的权重。接下来有N行,每一行以如下的格式:
Name1 Name2 Time
Name1和Name2是通话的两个人的名字。Time是通话时长。每一个名字是从A-Z中选取的三个字母组成的字符串。时长是不大于1000分钟的正整数。
输出
对每一个测试样例,第一行输出总的团伙数。接下来对每一个团伙,在一行中输出团伙头目的名字和团伙的人数。每一个团伙的头目保证独一无二。输出必须以团伙头目名字的字母顺序排列。

解题思路:

本题目可以利用图来解决。以名字作为顶点,这个人总的通话时间为顶点信息,通话纪录为边,通话时长为边的权重。图建立完成后用DFS进行遍历找出连通分量并计算连通分量的权重值和顶点数量,连通分量的权重值(即总的通话时长)大于K且顶点大于(即人数)3就为一个团伙,顶点值最大的即为这个团伙的头目。

个人认为有一下几个难点:

  1. 图的建立
    因为选择名字为图的顶点,而名字是字符串变量,但通常情况下我们使用二维数组建图顶点应为整型变量,字符串变量不方便建图,所以需要把字符串变量和整型变量进行映射才能建图。在本题中使用C++中的map进行映射。
  2. 通话总时长的计算
    题目描述中判断是否是一个团伙的依据是这群人的总人数是否大于2且通话时间大于给定值。总人数及连通分量的顶点数用DFS求连通分量时每访问一个顶点人数增加一个即可求出。通话总时长及连通分量的每一条边的权重之和,但用普通的DFS求带环图的连通分量时会少访问一条边及无法求出总的通话时长。
    通过参考这篇博客:https://www.cnblogs.com/sqmax/p/10084360.html终于找到了改进DFS的方法,求出了总的通话时长。具体改进方法参见上述博客。
  3. 排序
    不知道对这道题有没有影响,PAT中涉及到排序的尽量使用快速排序,避免程序运行超时。

本题解题所使用的语言是C++,IDE为VisualStudio2015

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#pragma warning(disable:4996);	//在VisualStudio避免使用scanf函数时编译报错,本题未使用scanf函数
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<string>
#include<map>
#define N 3000
#define INIF 40000

using namespace std;

/*
本题选择了图的方式进行解决,图的顶点为人名,为了便于图的建立和
最后的输出需要建立字符串类型的名字变量和整型变量之间的双向映射
*/

map<string, int> stringToInt; //把字符串类型的变量映射为整型变量
map<int, string> intToString; //把整型变量映射为字符串变量

int G[N][N] = { 0 }; //用整型二维数组存储图值为通话时间
int vertex[N] = { 0 }; //顶点数组值为这个人总的通话时间
int totalName = 1; //记录总的名字数目用于把名字和整型变量相映射以便建图
int visited[N] = { 0 }; //访问标记数组用于DFS
int head; //用于记录头目
int totalMember; //用于记录成员总数
int totalNumber = 1; //用于记录总的团伙数目,因为快排需要使用第一个位置即0做哨兵,所以初值从1开始
int weight; //权值

/*结果结构体数组,每一个结果包含头目的名字(用数字代替,输出时映射为名字)和成员数量*/
struct result
{
int head;
int totalMember;
}results[N];

/*改进后的深度优先搜索*/
void DFS(int G[][N], int v)
{
visited[v] = 1;
totalMember++; //访问一个顶点成员数量+1
if (vertex[v]>vertex[head]) //如果该成员的通话时长大于之前记录的头目的时长,更新头目为此顶点
{
head = v;
}

for (int w = 1; w < totalName; w++)
{
if (G[v][w]) //改进后的DFS先判断是否有边
{
weight += G[v][w]; //权值+=边的值
G[v][w] = G[w][v] = 0; //访问结束后把边删除
if (!visited[w])
{
DFS(G, w);
}
}
}
}


/*快速排序,对结果数字按字母序排序*/
int partition(int low, int high)
{
results[0] = results[low];

string key = intToString[results[low].head];

while (low<high)
{
while (low<high&&intToString[results[high].head] >= key)
{
high--;
}
results[low] = results[high];
while (low<high&&intToString[results[low].head] <= key)
{
low++;
}
results[high] = results[low];
}
results[low] = results[0];
return low;
}

void QSort(int low, int high)
{
if (low<high)
{
int loc = partition(low, high);
QSort(low, loc - 1);
QSort(loc + 1, high);
}
}

void QuickSort(int n)
{
QSort(1, n);
}


int main(void)
{
int n, k;


cin >> n >> k;

for (int i = 1; i <= n; i++)
{
string name1, name2;
int time;

cin >> name1 >> name2 >> time;

/*map中的初始值为0,可以以此判断名字是否是第一次出现
对第一次出现的名字进行映射并把边加入图中,对已经出现的
名字直接权值相加*/
if (stringToInt[name1])
{
if (stringToInt[name2])
{
G[stringToInt[name1]][stringToInt[name2]] += time;
G[stringToInt[name2]][stringToInt[name1]] += time;
vertex[stringToInt[name1]] += time;
vertex[stringToInt[name2]] += time;
}
else
{
stringToInt[name2] = totalName++;
intToString[stringToInt[name2]] = name2;
G[stringToInt[name1]][stringToInt[name2]] = time;
G[stringToInt[name2]][stringToInt[name1]] = time;
vertex[stringToInt[name1]] += time;
vertex[stringToInt[name2]] += time;
}
}
else
{
stringToInt[name1] = totalName++;
intToString[stringToInt[name1]] = name1;
if (stringToInt[name2])
{
G[stringToInt[name1]][stringToInt[name2]] = time;
G[stringToInt[name2]][stringToInt[name1]] = time;
vertex[stringToInt[name1]] += time;
vertex[stringToInt[name2]] += time;
}
else
{
stringToInt[name2] = totalName++;
intToString[stringToInt[name2]] = name2;
G[stringToInt[name1]][stringToInt[name2]] = time;
G[stringToInt[name2]][stringToInt[name1]] = time;
vertex[stringToInt[name1]] += time;
vertex[stringToInt[name2]] += time;
}
}
}

for (int i = 1; i < totalName; i++)
{
if (!visited[i])
{
head = i; //头目初始值设为搜索起点
totalMember = 0; //成员数目初始值为0
weight = 0; //权值初始值为0
DFS(G, i);
if (totalMember>2&&weight>k) //成员数目大于2且通话时长大于给定值即为一个团伙加入结果数组
{
results[totalNumber].head = head;
results[totalNumber].totalMember = totalMember;
totalNumber++;
}
}
}

QuickSort(totalNumber-1);

cout << totalNumber-1 << endl;

for (int i = 1; i < totalNumber; i++)
{
cout << intToString[results[i].head] << ' ' << results[i].totalMember << endl;
}

system("pause");
return 0;
}

总结

在做编程题时C++中的STL会有很大帮助,应该要熟练运用,如本题的map就可以解决字符串变量建图的问题。不同的题目需要对DFS等基本知识做不同的处理。如本题用基本的DFS会少访问一条边。

参考博客:https://www.cnblogs.com/sqmax/p/10084360.html