之前看王道论坛的机试指南这本书时也碰到过这题,当时没有做出来,现在一边过了~看来练习了一段时间效果还是很明显的,坚持!
这题的核心就是并查集,并查集最直观的作用就是求连通子图。
首先,我们定义一个数组,用双亲表示法来表示各棵树。
int Tree[N];
用Tree[i]来表示节点i的双亲节点,若Tree[i]为-1,则表示该节点不存在双亲节点,即节点i为其所在的树的根节点。最开始我们要将Tree[i]全部初始化为-1,这时的图只有一个个孤立的节点,后面读取边时再一步步更新。
为了查找节点x所在树的根节点,定义如下函数。为了在查找过程中添加路径压缩的优化(避免形成的连通子图是一个链表而不是一棵均匀的树,如果是一个链表,那么查找根节点的复杂度将上升。)
int findRoot(int x)
{
if (Tree[x] == -1)
{
return x;
}
else
{
int tmp = findRoot(Tree[x]);
Tree[x] = tmp;
return tmp;
}
}
上面Tree[x] = tmp就是将当前节点的双亲节点设置为查找返回的根节点编号,也可以不要这句,但为了实现上面提到的压缩路径,就加上这句,就是在往上一步步找根节点的过程中,将路上遇到的中间节点改为直接指向根节点。
以上就是并查集的基本过程。可以想到,最后,只要遍历一遍Tree[i],有多少元素等于-1,就有多少个连通子图,也就是多少个gang。但是,这题有额外的要求,也需要额外的输出。比如团伙中人员数量,团伙的老大,团伙总的通话时长等,这些不仅限制着是否为团伙,也是题目要求我们输出的信息。怎么办呢?
需要多少信息就新建多少数组,在并查集的过程中更新这些信息即可。
int Tree[2000]; // 记录每个节点的父结点
int head[2000]; // 记录每个团伙的首领,当然,我们只关心作为根节点的那些元素的值
int gangTime[2000]; // 记录每个团伙的总的通话时间,当然,我们只关心作为根节点的那些元素的值
int member[2000]; // 记录每个团伙的总人数,当然,我们只关心作为根节点的那些元素的值
int personTime[2000]; //记录每个人的总通话时间
该题并查集更新的具体方法不再赘述,请见最后的AC代码。
这里还有一个小问题就是人员的编号,因为人员是以字符串来表示的,在编程过程中很难表示和操作。可以使用C++的STL中的map将字符串和数字映射起来,为每个人员分配一个数字。
map<string, int> name2num;
map<int, string> num2name;
映射过程如下:
// size是一个在前面定义的全局变量
string a, b;
a.resize(4); // 使用scanf为string赋值前要resize
b.resize(4);
int t;
scanf("%s%s%d", &a[0], &b[0], &t);
// 更新每个人的总通话时长、更新map映射
if (name2num.count(a) == 0) // 如果map不存在a人名,则为其分配下一个数字
{
name2num[a] = size;
num2name[size] = a;
size++;
}
personTime[name2num[a]] += t;
if (name2num.count(b) == 0)
{
name2num[b] = size;
num2name[size] = b;
size++;
}
personTime[name2num[b]] += t;
// 最后得到的size值就是人员的总数
贴上AC代码:
#include<stdio.h>
#include<map>
#include<string>
#include<string.h>
#include<vector>
#include<algorithm>
using namespace std;
int Tree[2000];
int head[2000];
int gangTime[2000];
int member[2000];
int personTime[2000];
struct G
{
string head;
int member;
};
vector<G> gangs;
map<string, int> name2num;
map<int, string> num2name;
bool cmp(G G1, G G2)
{
return (strcmp(G1.head.c_str(), G2.head.c_str()) < 0);
}
int findRoot(int x)
{
if (Tree[x] == -1)
{
return x;
}
else
{
int tmp = findRoot(Tree[x]);
Tree[x] = tmp;
return tmp;
}
}
int main()
{
int size = 0;
int N, K;
scanf("%d%d", &N, &K);
//init
for (int i = 0; i < 2000; i++)
{
Tree[i] = -1;
}
for (int i = 0; i < 2000; i++)
{
head[i] = i;
}
for (int i = 0; i < 2000; i++)
{
member[i] = 1;
}
for (int i = 0; i < N; i++)
{
string a, b;
a.resize(4);
b.resize(4);
int t;
scanf("%s%s%d", &a[0], &b[0], &t);
// 更新每个人的总通话时长、更新map映射
if (name2num.count(a) == 0)
{
name2num[a] = size;
num2name[size] = a;
size++;
}
personTime[name2num[a]] += t;
if (name2num.count(b) == 0)
{
name2num[b] = size;
num2name[size] = b;
size++;
}
personTime[name2num[b]] += t;
int at = findRoot(name2num[a]);
int bt = findRoot(name2num[b]);
if (at == bt)
{
gangTime[at] += t;
if (personTime[name2num[a]] > personTime[head[at]])
{
head[at] = name2num[a];
}
if(personTime[name2num[b]] > personTime[head[at]])
{
head[at] = name2num[b];
}
}
else
{
Tree[at] = bt;
member[bt] += member[at];
head[bt] = (personTime[head[bt]] > personTime[head[at]]) ? head[bt] : head[at];
gangTime[bt] += gangTime[at];
gangTime[bt] += t;
if (personTime[name2num[a]] > personTime[head[bt]])
{
head[bt] = name2num[a];
}
if (personTime[name2num[b]] > personTime[head[bt]])
{
head[bt] = name2num[b];
}
}
}
for (int i = 0; i < size; i++)
{
if (Tree[i] == -1 && member[i] > 2 && gangTime[i] > K)
{
G g;
g.head = num2name[head[i]];
g.member = member[i];
gangs.push_back(g);
}
}
sort(gangs.begin(), gangs.end(), cmp);
printf("%d\n", gangs.size());
for (int i = 0; i < gangs.size(); i++)
{
printf("%s %d\n", gangs[i].head.c_str(), gangs[i].member);
}
return 0;
}