PAT_甲级_1034 Head of a Gang

题目大意:

给出若干人之间的通话长度,按照这些通话将他们分成若干个组。现在给定一个犯罪团伙,而该组内点权最大的人视为头目。要求输出犯罪团伙的个数,并且按照头目姓名字典序从小到大的顺序输出每个犯罪团伙的头目姓名和成员个数。

算法思路:

这道题的题意得先理解清楚,

In each gang, the one with maximum total weight is the head.

这句话的意思是$maximum$ $total$ $weight$的点为$head$,但是$weight$在题目中是边权,所以这里针对题目的案例进行手动绘图和假设后,比较合理的解释就是一个点的$total$ $weight$指的是该点出边和入边的边权的和。并且根据语义来说,该图应该是一个无向图,因为通话是建立双方的连接.

接下来我们要知道我们需要获得那些信息可以求解这些问题,以及通过什么方式获得这些数据,题目要求输出每个犯罪团伙的头目姓名和成员个数,首先犯罪团伙的定义是一个团伙(连通分量)其人数大于2并且总边权的和大于K,然后头目的定义是组内点权最大的人视为头目,那么我们要获得信息如下:

 1)组内的人数(连通分量的结点个数)
 2)组内的通话时间总和(连通分量的总边权)
 3)组内通话时间最长的人(连通分量中点权最大的结点编号)

现在知道了需要获得上面3条信息后,就得知道如何获得3条信息,这些信息都与连通分量有关,在图中我们获得每个结点或者连通分量信息的方法就是遍历,这里使用了DFS。

在进行DFS遍历之前得先对数据进行预处理:
  • 输入的名字是字符串,我们遍历的是结点编号,同时输出的时候也是字符串,那么得建立名字--->结点编号,结点编号----->名字的双向映射,这里使用unordered_map indexToName存储编号到姓名的映射,unordered_map nameToIndex存储姓名到编号的映射,具体的做法就是先使用numPerson=0表示当前已知结点的个数,同时也是新加入图中的结点的编号,在每次输入数据的时候先判断当前人的名字是否出现过,如果没有出现就建立名字和结点编号的映射和编号和名字的反映射,否则就取出该名字的编号,紧接着就根据编号,累计点权和边权。
DFS遍历过程:

每一次DFS需要获得以上3条信息并且每次遍历一个连通分量,我们设置参数start,number,total_time,head分别代表当前访问结点,当前连通分量的结点个数,边权总和和最大点权的编号,每次访问结点就累加连通分量的结点个数++number,并且判断当前结点的点权和head的点权哪个更大,如果当前结点更大,更新head,然后再遍历其邻接点,这里有个注意点,因为有可能是有环的图,所以为了计算总边权,我们先累加当前结点和邻接点的边权,然后再判断邻接点是否可以访问。并且每一条边只能访问一次,所以在每次访问后,就将该条边进行赋值为0,这样保证在重复访问的时候总边权计算不会出错,之所以先累计边权,看下图即可:
image.png

接下来可就是遍历整个图形获得所有连通分量的那3个信息,对于每一个顶点只要没有访问就调用DFS访问该连通分量,然后将获得的信息进行处理,只要number>2&&total_time>K说明是犯罪团伙,那么记录犯罪团伙的头目和人数,这里采用map result记录,因为可以自动排序,具体操作为result[indexToName[head]] = number;
最后按照要求输出即可

注意点;

  • 1.测试点3出现段错误,原因在于数组开的太小了,n条边最多有2*n个结点,至少开到2000以上
  • 2.没有排序测试点2和5错误
  • 3、没有考虑到一个团伙有2个点权一样大输出字典序小的测试点5错误,直接在遍历图的时候按照结点编号从小到大进行遍历就可以避免
  • 4、题目说A name is a string of three capital letters chosen from A-Z,并不是名字都是AAA,BBB这种3个字母都一样的名字,只不过样例给的是这样的,有误导作用,不然只能过2个测试点(不要问我是怎么知道的,(╥╯^╰╥)) ,所以使用从0开始依次递增赋值编号是最方便的,字符串hash这里不太适用,容易超内存。

提交结果:

image.png

很多网上的代码只能通过PAT的测试点,无法通过牛客网的测试,本人提供的代码,均能通过.

AC代码:

#include 
#include 
#include 
#include 
#include 

using namespace std;

int N,K;//边数和阈值
bool visited[2000];//访问标记数组
int G[2000][2000];// 邻接矩阵,其值为边权
int node_weight[2000];// 点权
unordered_map indexToName;// 存储编号到姓名的映射
unordered_map nameToIndex;// 存储姓名到编号的映射
map result;// 每一个gang的head和人数集合
int numPerson = 0;//总人数

void DFS(int start,int &number,int &total_time,int &head){
    visited[start] = true;// 访问该节点
    ++number;// 当前连通分量节点数目加一
    if(node_weight[head]i的边只能访问一遍,得置为0,防止重复计算影响结果
            G[start][i] = G[i][start] = 0;
            if(!visited[i]){
                DFS(i,number,total_time,head);
            }
        }
    }
}

void DFSTraverse(){
    for (int i = 0; i < numPerson; ++i) {
        if(!visited[i]){
            int number = 0;// 每一个gang的人数
            int total_time = 0;// 每一个gang的总通话时长
            int head = i;// 每一个gang的head,初始得是当前访问连通块中的点,不能是0
            DFS(i,number,total_time,head);
            if(number>2&&total_time>K){
                // 当前连通分量是一个gang
                result[indexToName[head]] = number;
            }
        }
    }
    printf("%lu\n",result.size());
    map::iterator it;
    for(it=result.begin();it!=result.end();++it){
        printf("%s %d\n",it->first.c_str(),it->second);
    }
}

int main()
{
    scanf("%d %d",&N,&K);
    string a,b;
    int time,index_a,index_b;
    for (int i = 0; i < N; ++i) {
        cin>>a>>b>>time;
        if(nameToIndex.find(a)==nameToIndex.end()){
            // 第一次出现,赋予编号
            index_a = numPerson++;
            nameToIndex[a] = index_a;
            indexToName[index_a] = a;
        } else {
            // 不是第一次出现,取出编号
            index_a = nameToIndex[a];
        }
        if(nameToIndex.find(b)==nameToIndex.end()){
            // 第一次出现,赋予编号
            index_b = numPerson++;
            nameToIndex[b] = index_b;
            indexToName[index_b] = b;
        } else {
            // 不是第一次出现,取出编号
            index_b = nameToIndex[b];
        }
        // 边权
        G[index_a][index_b] += time;
        G[index_b][index_a] += time;
        // 点权
        node_weight[index_a] += time;
        node_weight[index_b] += time;
    }
    DFSTraverse();
    return 0;
}

你可能感兴趣的