算法笔记练习 9.6 并查集 问题 D: More is better - 超级详细的思路讲解

算法笔记练习 题解合集

本题链接

题目

题目描述
Mr Wang wants some boys to help him with a project. Because the project is rather complex, the more boys come, the better it will be. Of course there are certain requirements.Mr Wang selected a room big enough to hold the boys. The boy who are not been chosen has to leave the room immediately. There are 10000000 boys in the room numbered from 1 to 10000000 at the very beginning. After Mr Wang’s selection any two of them who are still in this room should be friends (direct or indirect), or there is only one boy left. Given all the direct friend-pairs, you should decide the best way.

输入
The first line of the input contains an integer n (0 ≤ n ≤ 100 000) - the number of direct friend-pairs. The following n lines each contains a pair of numbers A and B separated by a single space that suggests A and B are direct friends. (A ≠ B, 1 ≤ A, B ≤ 10000000)

输出
The output in one line contains exactly one integer equals to the maximum number of boys Mr Wang may keep.

样例输入

3
1 3
1 5
2 5
4
3 2
3 4
1 6
2 6

样例输出

4
5

思路

本题在并查集的基础上要求额外的一个信息:集合中结点的个数。用一个manCnt数组来记录即可,一开始所有结点都是 father 结点,所以manCnt数组中所有结点的值都应该被初始化成 1(实际上不用初始化所有结点,详见优化时间的第 2 点)。

每当Union函数把 father 结点fa合并入 father 结点fb的时候,令manCnt[fb] += manCnt[fa];即可更新数据。

优化时间

优化时间的思路参考了这篇博客:2130 Problem D More is better

这个题有两个地方可以显著地降低耗时,第 2 点是重点:

  1. 关于max的求法,不用把所有 father 结点的人数都算完然后最后再遍历一遍,而是每次更新人数的时候顺便更新max(这一步放在Union函数即可);
  2. 只初始化需要用到的数据。我一开始把father数组和manCnt数组在每一轮输入中都无脑初始化所有元素,由于这个题数据量不算小,这一步非常耗时。事实上对于每一轮输入我们都只需要处理出现过的结点即可,未出现的结点无视即可。代码中init函数负责初始化。
    但是,如果每输入一对数据,随后立即进行Union的话可能会造成重复初始化的问题,例如a结点已经是一个大集合的 father 结点,此时又输入数据a b,那么manCnt[a]会被错误地初始化为 1,于是我们就需要先把所有输入保存下来,全部初始化完后再处理,代码中的input负责暂存输入。

代码

最终版本(AC,codeup 上耗时约 60ms)

#include <iostream>
#include <vector>
#include <utility>
#define MAX 10000001
using std::vector;
using std::pair;

int father[MAX], manCnt[MAX], max;

int findFather(int x) {
	int f = x, temp;
	while (f != father[f])
		f = father[f];
	while (x != father[x]) {
		temp = x;
		x = father[x];
		father[temp] = f; 
	} 
	return f;
} 
void Union(int a, int b) {
	int fa = findFather(a), fb = findFather(b);
	if (fa != fb) {
		father[fa] = fb;
		manCnt[fb] += manCnt[fa];
		if (manCnt[fb] > max)
			max = manCnt[fb];
	}
}
inline void init(int a) {
	father[a] = a;
	manCnt[a] = 1; 
} 

int main() {
	int n, a, b;
	while (scanf("%d", &n) != EOF) {
		if (n == 0) {
			printf("1\n");
			continue; 
		} 
		max = 0;
		vector<pair<int, int> > input;
		for (int i = 0; i < n; ++i) {
			scanf("%d%d", &a, &b);
			input.push_back( {a, b} ); 
			init(a);
			init(b);
		}
		for (int i = 0; i < input.size(); ++i)
			Union(input[i].first, input[i].second); 
		printf("%d\n", max); 
	} 
	return 0;
} 

仅改进 max 求法版本(AC,codeup 上耗时约 700ms)

#include <iostream>
#define MAX 10000001

int father[MAX], manCnt[MAX], max;

int findFather(int x) {
	int f = x, temp;
	while (f != father[f])
		f = father[f];
	while (x != father[x]) {
		temp = x;
		x = father[x];
		father[temp] = f; 
	} 
	return f;
} 
void Union(int a, int b) {
	int fa = findFather(a), fb = findFather(b);
	if (fa != fb) {
		father[fa] = fb;
		manCnt[fb] += manCnt[fa];
		if (manCnt[fb] > max)
			max = manCnt[fb];
	}
} 

int main() {
	int n, a, b;
	while (scanf("%d", &n) != EOF) {
		if (n == 0) {
			printf("1\n");
			continue; 
		} 
		max = 0;
		for (int i = 1; i <= MAX; ++i) { 
			father[i] = i;
			manCnt[i] = 1;
		}
		for (int i = 0; i < n; ++i) {
			scanf("%d%d", &a, &b);
			Union(a, b);
		}
		printf("%d\n", max); 
	} 
	return 0;
} 

最初版本(超时,codeup 上耗时约 3000ms)

#include <iostream>
#include <algorithm>
#define MAX 10000001
using std::vector;

int father[MAX], manCnt[MAX];

int findFather(int x) {
	int f = x, temp;
	while (f != father[f])
		f = father[f];
	while (x != father[x]) {
		temp = x;
		x = father[x];
		father[temp] = f; 
	} 
	return f;
} 
void Union(int a, int b) {
	int fa = findFather(a), fb = findFather(b);
	if (fa != fb) { 
		father[fa] = fb;
		manCnt[fb] += manCnt[fa];
	}
} 

int main() {
	int n, a, b, max;
	while (scanf("%d", &n) != EOF) {
		if (n == 0) {
			printf("1\n");
			continue; 
		} 
		max = 0;
		for (int i = 1; i < MAX; ++i) { 
			father[i] = i;
			manCnt[i] = 1; 
		}
		for (int i = 0; i < n; ++i) {
			scanf("%d%d", &a, &b);
			Union(a, b); 
		}
		for (int i = 1; i < MAX; ++i)
			max = std::max(manCnt[findFather(i)], max);
		printf("%d\n", max); 
	} 
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值