并查集的使用(4):head of a gang

题目网址:head of a gang

需要注意这一题,与之前那些并查集的题目相比难度增大很多,需要考虑的因素增多。

题目大意:给定n和k,n组通话(通话双方的name和通话时间),我们需要做的是找出属于一个团体的人,并且这个团体的人数必须大于2,且团体的总的通话时间需要大于k,要求输出最终满足条件的gang的boss的name和这个gang的人数!(按照字母序输出)

分析:

(1)首先使用map来存储每一个人的name和他的编号之间的映射关系:如果之前没有使用过map的读者可以看看我的博客STL-map那里面有一些简单的介绍;

(2)其次需要考虑的是一个gang的人数需要大于2,这个我是采用num数组来实现的,每一次合并我需要将根节点的num也进行合并:比如parent[a]=b;num[b]+=num[a];我把a这个根节点表示的集合的人数加上到b这个根节点表示的集合的人数上;

(3)接着还需判断这个gang的通话时间是不是大于k,我采用tsum数组来存,最终根节点保存的就是这个gang的通话时间,需要大于k才满足;

(4)gang的条件就是需要同时满足(2)和(3)的要求,只有判断是gang了,才需要找boss!

(5)最后需要考虑的是如何判断一个集合的人谁是那个大boss,我利用sum数组来存储每一个人的总的通话时间,那我们再输入n对通话的过程中,给通话双方的sum都加上输入的时间;再找到那些gang的时候,我只需要找到gang的根节点(不一定是boss),利用这个根节点判断哪些是属于这个集合的人数,并且保存最大通话时间的那个人,那个人就是boss;

(6)你需要按照字母序输出,那么我设置了一个结构体,保存每一个gang的boss的name和这个gang的人数,在输出之前进行sort排序就行!

经过分析基本都解决了,下面给出源码,源码中也有注释!

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
#include<map>
using namespace std;
int sum[2010];//表示每个人的通话时长,每输入一组数据就将涉及到的两个人的sum加上输入的time
int parent[2010];//表示根节点,用来表示十分属于一个集合
int num[2010];//统计每一个集合的元素的个数,每个集合的人数需要大于2个人
int tsum[2010];//统计每一个集合的通话总时长,总时长需要大于k
int findroot(int x)
{
	if (parent[x] != x)
		parent[x] = findroot(parent[x]);
	return parent[x];
}
struct node{
	string name;
	int size;
	int calltime;
	bool operator <(const node &a)const{
		return name < a.name;//用来对name进行字母序输出的排序
	}
}nnn[1010];//用来保存每一个gang的头头的name和这个gang的总人数,以及这个gang的头头的通话时间,其实时间存不存无所谓,也没有要求输出
int main()
{
	map<string, int> m;
	int n, k;//n:表示总的通话的对数,k表示那个阙值
	string str1;
	string str2;
	int t;
	while (cin >> n >> k)
	{
		m.clear();
		int cnt = 0;//表示输入的人数
		for (int i = 0; i <= 2 * n; i++)
		{//初始化的过程,不能缺少!
			parent[i] = i;
			sum[i] = 0;
			tsum[i] = 0;
			num[i] = 1;//每一个人开始以自己为根,组成了只有自己的一个集合,所以这个集合的人数设置为1
		}
		while (n--)
		{
			cin >> str1 >> str2;
			cin >> t;
			int a, b;//a:保存str1的标签,b:保存str2的标签
			if (m.find(str1) == m.end())
			{//查找当前输入的第一个人是否已经在这个map里面有映射关系,如果没的话,需要插入map,并且分配一个他的数字标签
				m.insert(make_pair(str1, cnt));
				sum[cnt] += t;//输入的每一个人都需要更新自己的sum数组,给自己的通话时间加上t,以便找出这个组的boss
				a = cnt;
				cnt++;
			}
			else
			{
				a = m.find(str1)->second;
				sum[a] += t;
			}
			if (m.find(str2) == m.end())
			{
				m.insert(make_pair(str2, cnt));
				sum[cnt] += t;
				b = cnt;
				cnt++;
			}
			else
			{
				b = m.find(str2)->second;
				sum[b] += t;
			}
			a = findroot(a);//合并两个集合
			b = findroot(b);
			if (a != b)
			{
				parent[a] = b;
				num[b] += num[a];//合并这两个集合的时候,需要合并这两个组的人数
				tsum[b] += tsum[a];
			}//tsum表示这个组的通话时间,所以处理起来比较复杂,只有两个节点不是一个集合的时候才需要另外加上之前那个组boss的时间,否则直接加上t即可
			tsum[b] += t;
		}
		int ans = 0;
		int gang[2010];//用来保存满足条件的gang,两点要求gang人数大于2,且这个gang的tsum需要大于k
		for (int i = 0; i < cnt; i++)
		{//gang数组里面保存的是这个集合的根节点,但是需要注意并非一定就是boss!还需看这个组成员的最大的通话时间
			if (parent[i] == i&&num[i] > 2&&tsum[i]>k)
			{
				gang[ans] = i;
				ans++;
			}
		}
		cout << ans << endl;//输出满足条件的gang的个数
		/*map<string, int>::iterator it = m.begin();
		for (; it != m.end(); it++)
		{//读者可以把这里注释去掉,看看每一个节点的name和他的编号,以及每一个人的通话时间和他的父亲节点,这个父亲节点并非一定是这个集合的根节点哈!!
			cout << it->first << " " << it->second<<" " << sum[it->second]<<" "<<parent[it->second] << endl;
		}*/
		int i;
		for (i = 0; i < ans; i++)
		{
			int MAX = -1;
			int MAXpos = gang[i];//当前假设最大的打电话时间的人的下标为这个组的根节点,但是必须知道根节点不一定通话时间最长!
			for (int j = 0; j < cnt; j++)
			{//循环找到这个组里面的通话时间最长的那个人的下标和最大的通话时间
				if (findroot(j) == gang[i] && sum[j]>MAX)
				{
					MAX = sum[j];//max的通话时间
					MAXpos = j;//max 的通话的人的下标
				}
			}
		//	cout<<"maxpos:" << MAXpos << endl;
			map<string, int>::iterator it = m.begin();
			for (; it != m.end(); it++)
			{
				if (it->second == MAXpos)//找到这个人的名字,放回去到node数组中
				{
					//cout << it->first << " " << num[gang[i]] << endl;
					node nn;//错误之处:因为我们需要按照字母序排序输出,所以必须保存进入node数组,经过按照name的排序在输出,否则出错!
					nn.name = it->first;
					nn.size = num[gang[i]];
					nn.calltime = MAX;
					nnn[i] = nn;
				}
			}
		}
		sort(nnn, nnn + ans);//按照名字的字母顺序输出!
		for (int i = 0; i < ans; i++)
		{
			cout << nnn[i].name << " " << nnn[i].size << endl;
		}
	}
	//system("pause");
	return 0;
}




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值