前言
这题算是我第一次写图相关的算法题,也是第一次用map容器,需要记录的点很多,因此在这里稍作总结,方便后续回忆。
题目
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
题意
通过电话号码找匪帮。只要彼此之间有通话记录,就视作二人在一个帮派,一个帮派即一群能通过电话记录联系起来的人。通话时间为权重,只有总通话时间超过k的帮派才是匪帮。给出通话双方的姓名和时长,找出来有几个匪帮,头目(权重最重的人)是谁,这个匪帮有几个人。
分析
本题即找出有多少个连通分量,故而应采取dfs遍历(每次遍历一个连通分量),并记录权重。
需要注意的地方有:
1、如何将人名映射到图中结点;
2、如何记录总权重,如何找出头目,如何记录人数
3、如何表示这个图
4、如何读取数据,保存数据
思路
1、使用两个map容器,一个是string映射到int,从而将姓名转化为图中结点的编号,一个是int转string,将结点编号转为姓名,从而方便记录匪帮头目。
2、在dfs的过程中,通过引用的方式,传递成员数量和总权重,每层递归都更新即可。而对于头目,事先存储好每个人的权重(用一个weight[]数组即可),每层递归比较一下即可。再有,计算总权重时要注意,进入下一次递归前要把当前点到即将递归点之间的边抹去,原因是这样:假设1和2有电话记录,2和3有记录,3和1有记录,那么根据dfs的规则,在遍历到1的时候,dfs中的循环部分,总权重必须加上1和3的通话时长,加完后进行1到3的遍历,但遍历到3时则不可加上时长,也不能回到1遍历,因此必须首先对边长(即通话时长)进行判断,有边时加上时长并抹去这个边,确保不重不漏,再对是否访问过进行检查。
3、这个图的每个结点,其编号通过map与成员姓名相对应。结点的权重,代表这个人的总通话时间。结点的边,代表边的两个端点通过电话,边的权重代表二人的通话时间。
4、在读取数据时,每读一行,找到通话双方的姓名对应的编号,如果不存在(即首次出现),则将其编号定义为目前的人数(同时将这个人数对应的字符串保存为姓名);找到编号后,将对应的结点权重、边权重更新即可。
5、实际进行遍历时,每个联通分量的遍历伊始,将成员数和总权重定义为0,头目定义为第一个点,进行递归,每次递归成员数++,和头目做比较,然后进入到这个点的循环递归,循环中先把存在的边权重加入总权重,再对未访问过的点进行递归即可。
代码
代码基本和《算法笔记》上的一样,放在这里只是为了方便自己日后参考。
#include <map>
#include <string>
#include <iostream>
using namespace std;
const int maxn = 2010;
const int INF = 1000000000;
map<int,string> intTostring;
map<string,int> stringToint;
map<string,int> Gang;//head->人数
int G[maxn][maxn] = {0},weight[maxn] = {0};
int n,k,numperson = 0;
bool vis[maxn] = {false};
void DFS(int nowVisit,int &head,int &numMember,int &totalValue){
numMember++;
vis[nowVisit] = true;
if(weight[nowVisit] > weight[head]){
head = nowVisit;
}
for(int i = 0;i < numperson;i++){
if(G[nowVisit][i] > 0){
totalValue += G[nowVisit][i];
G[nowVisit][i] = G[i][nowVisit] = 0;
if(vis[i] == false)
DFS(i,head,numMember,totalValue);
}
}
}
void DFSTrave(){
for(int i = 0;i < numperson;i++){
if(vis[i] == false){
int head = i,numMember = 0,totalValue = 0;
DFS(i,head,numMember,totalValue);
if(numMember > 2 && totalValue > k){
Gang[intTostring[head]] = numMember;
}
}
}
}
int change(string str){
if(stringToint.find(str) == stringToint.end()){
stringToint[str] = numperson;
intTostring[numperson] = str;
return numperson++;
}else{
return stringToint[str];
}
}
int main(){
int w;
string str1,str2;
cin >> n >> k;
for(int i = 0;i < n;i++){
cin >> str1 >> str2 >> w;
int id1 = change(str1);
int id2 = change(str2);
weight[id1] += w;
weight[id2] += w;
G[id1][id2] += w;
G[id2][id1] += w;
}
DFSTrave();
cout << Gang.size() << endl;
map<string,int>::iterator it;
for(it = Gang.begin();it != Gang.end();it++){
cout << it->first << ' ' << it->second << endl;
}
return 0;
}
总结
- 对于给姓名而非编号的题目,要用两个map容器,保存int到string和string到int的转换。
- map容器自动按照key,由小到大排序。
- 如何判断一个key存不存在?看map.find(key)是否等于map.end()。
- 如何遍历map?定义一个迭代器。
- 若是要对边进行操作,则为保证不重不漏,判定条件要分开,而非vis[i] == false && G[v][i]>0