您的位置:首页 > 其它

pat甲级1034. Head of a Gang (30)

2018-03-15 23:02 477 查看

1034. Head of a Gang (30)

时间限制100 ms
内存限制65536 kB
代码长度限制16000 B
判题程序Standard作者CHEN, Yue
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 threshold, respectively. Then N lines follow, each in the following format:Name1 Name2 Timewhere 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

题意分析:

给出多个人之间的通话长度,按照这些通话将他们分成若干个组,各个组的总权值是该组内所有通话长度之和,每个人的权值是其参与的所有通话长度之和。
就给出的样例而言,所有的数据可以分成3个集合,分别是:(AAA、BBB、CCC)、(DDD、EEE)、(FFF、GGG、HHH)
其中各个结点的权值依次是(按字母排序):70、30、40、 75、 75、 40、50、 30
3个组的总权值依次是70、75、60
所以如果阈值是59,共有两个Gang,头目分别是AAA、GGG;如果阈值是70,则没有Gang

算法设计:

我们将人视作结点,将人与人之间的通话视作边。如果把权值都集中在结点中作为点权,相互之间有通话则按无向无权边来表示,我们就会发现这道题实质上是一道求图的连通
4000
分量问题。对于每个连通分量,将其中点权最大的结点视作该连通分量的头目,人数超过2且连通分量中所有人总权值超过给定阈值的连通分量即为Gang,输出该Gang的头目名字和连通分量中的结点数目。
凡涉及求连通分量的问题都有两种方法:并查集、深度优先遍历,我将会给出这两种方法的c++代码

注意点:

(1)题目中所给的N只是通话记录的数量,由于每个通话记录参与的人都有两个,所以人的总数最多可以为2*N,所以代码中定义各种针对人的数组的时候数组维度最少也要开到2*N
(2)题目最后要求如果有多个Gang,要按头目名字的字母顺序进行输出,可以把Gang的头目和对应的人数存储在一个map中,因为map可以按照键自动排序
(3)为了方便计算,可以使用map<string,int>将输入的名字映射到一个整数

使用并查集的C++代码:

#include<bits/stdc++.h>
using namespace std;
//定义并查集类
struct UFS{
int*father=nullptr;
int size=0;
//构造函数,定义一个维度为n的数组,并将元素初始化为其下标
UFS(int n){
father=new int
;
for(int i=0;i<n;++i)
father[i]=i;
size=n;
}
//析构函数
~UFS(){
delete []father;
}
//查找父亲结点并进行路径压缩
int findFather(int x){
if(x==father[x])
return x;
int temp=findFather(father[x]);
father[x]=temp;
return temp;
}
//合并两个集合
void unionSet(int a,int b){
int ua=findFather(a),ub=findFather(b);
if(ua!=ub)
father[ua]=ub;
}
};
//作为辅助求解的集合类
struct Set{
int head=-1;//头目
int weight=0;//集合的总权值
int num=0;//集合的人数
};
int main(){
int N,K;
scanf("%d%d",&N,&K);
unordered_map<string,int>STOI;//将名字映射到一个整数
vector<string>ITOS;//将整数映射到名字
int weight[2*N]={0};//点权
UFS ufs(2*N);//定义并查集
//读入数据
for(int i=0;i<N;++i){
getchar();
string s1,s2;
cin>>s1>>s2;
int w;
scanf("%d",&w);
//如果STOI中没有改名字,将名字加入STOI,并同步更新ITOS
if(STOI.find(s1)==STOI.cend()){
STOI[s1]=ITOS.size();
ITOS.push_back(s1);
}if(STOI.find(s2)==STOI.cend()){
STOI[s2]=ITOS.size();
ITOS.push_back(s2);
}
//更新点权
weight[STOI[s1]]+=w;
weight[STOI[s2]]+=w;
//合并相应集合
ufs.unionSet(STOI[s1],STOI[s2]);
}
Set gang[2*N];//辅助的计算Gang的数组
//遍历并查集中的集合更新各项参数
for(int i=0;i<ufs.size;++i){
int temp=ufs.findFather(i);//找到该集合根节点,并以该节点编号作为该集合的唯一标识
++gang[temp].num;//递增该集合人数
gang[temp].weight+=weight[i];//增加该集合总权值
//更新该集合头目
if(gang[temp].head==-1)
gang[temp].head=i;
else if(weight[i]>weight[gang[temp].head])
gang[temp].head=i;
}
map<string,int>result;//存储最终输出结果,利用map自动按头目名字排序
//遍历并查集中的集合找到符合条件的Gang
for(int i=0;i<N;++i)
if(gang[i].num>2&&gang[i].weight/2>K)
result.insert({ITOS[gang[i].head],gang[i].num});
//进行输出
printf("%d\n",result.size());
for(auto i=result.cbegin();i!=result.cend();++i)
printf("%s %d\n",(i->first).c_str(),i->second);
return 0;
}
使用深度优先遍历的C++代码:
#include<bits/stdc++.h>
using namespace std;
//作为辅助求解的集合类
struct Set{
int head=-1;//头目
int weight=0;//集合的总权值
int num=0;//集合的人数
};
const int MAXV=2005;
int weight[MAXV]={0};//点权
bool visit[MAXV]={false};//结点是否已被访问
Set gang[MAXV];//辅助的计算Gang的数组
vector<vector<int>>graph(MAXV);
void DFS(int v,int start){
visit[v]=true;//将该节点设置为已访问
++gang[start].num;//递增该集合人数
gang[start].weight+=weight[v];//增加该集合总权值
//更新该集合头目
if(gang[start].head==-1)
gang[start].head=v;
else if(weight[v]>weight[gang[start].head])
gang[start].head=v;
for(int i=0;i<graph[v].size();++i){
int temp=graph[v][i];
if(!visit[temp])
DFS(temp,start);
}
}
int main(){
int N,K;
scanf("%d%d",&N,&K);
unordered_map<string,int>STOI;//将名字映射到一个整数
vector<string>ITOS;//将整数映射到名字
//读入数据
for(int i=0;i<N;++i){
getchar();
string s1,s2;
cin>>s1>>s2;
int w;
scanf("%d",&w);
//如果STOI中没有改名字,将名字加入STOI,并同步更新ITOS
if(STOI.find(s1)==STOI.cend()){
STOI[s1]=ITOS.size();
ITOS.push_back(s1);
}if(STOI.find(s2)==STOI.cend()){
STOI[s2]=ITOS.size();
ITOS.push_back(s2);
}
//更新点权
weight[STOI[s1]]+=w;
weight[STOI[s2]]+=w;
//向图中增加无向边
graph[STOI[s1]].push_back(STOI[s2]);
graph[STOI[s2]].push_back(STOI[s1]);
}
//深度优先遍历
for(int i=0;i<2*N;++i)
if(!visit[i])
DFS(i,i);
map<string,int>result;//存储最终输出结果,利用map自动按头目名字排序
//遍历并查集中的集合找到符合条件的Gang
for(int i=0;i<N;++i)
if(gang[i].num>2&&gang[i].weight/2>K)
result.insert({ITOS[gang[i].head],gang[i].num});
//进行输出
printf("%d\n",result.size());
for(auto i=result.cbegin();i!=result.cend();++i)
printf("%s %d\n",(i->first).c_str(),i->second);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: