并查集——糖果

题目描述

链接:https://ac.nowcoder.com/acm/contest/9983/G
来源:牛客网

在一个幼儿园里面有 n 个小朋友,分别编号 1,2,…,n。在这些小朋友中有一些小朋友互为朋友关系,总共有 m 对朋友。作为幼儿园老师,你想买一些糖果分给小朋友,你知道第 i 个小朋友想要至少 a[i] 个糖果,否则他就会不开心。同时,如果一个小朋友得到的糖果数小于他某个朋友得到的糖果数,他也会不开心。请问你最少买多少糖果才能保证每个小朋友都不会不开心呢?

输入描述

第一行以空格分隔的两个整数 n, m。
第二行以空格分隔的 n 个正整数 a[i]。
接下来 m 行每行以空格分隔的两个正整数 u, v,代表 u 是 v 的朋友,v 是 u 的朋友。
1 <= n <= 1e6
0 <= m <= 1e6
1 <= a[i] <= 1e9
1 <= u,v <= n, u != v

输出描述

购买的最少糖果数以保证每个小朋友都不会不开心。

解题思路

这道题需要注意的是当 a和b是朋友,b和c是朋友,此时a和b的糖果数相等,b和c的糖果数也相等,即a,b,c的糖果数都相等,所以实际上他们相互都是朋友。综上,我们可以将他们并入一个同一个集合,运用并查集的方法解题。

心得

并查集的应用相对广泛,可以配合下面我个人对并查集的理解和代码来进行学习,之后相关的题目的解题都基本相同!

并查集

并查集可以简单拆分为合并、查询集合。所谓合并就是当两个不同集合中的两个元素有关系时,我们可以将这两个集合合并为一个集合。而查询就是我们可以查询两个元素是否在同一个集合。并查集通常使用一个数组f表示每个元素的父元素,然后编写Union函数改变数组f中元素的值,若两个元素 a,b 有关系则通过改变f中a或b的值模拟合并。Find函数是查询元素的“最年老”的父元素,我们还可以对Find函数进行优化,让每个寻找过自己“最年老”的父元素的元素的f值直接等于其“最年老”的父元素。

C++代码

#include
#include
#include
#include
#define ll long long
using namespace std;
const int maxn = 1e6 + 5;

ll a[maxn], f[maxn];

ll Find(ll x) {
	//1. 当x的父元素不是x时就查找x的父元素的父元素 
	//return f[x] == x ? : Find(f[x]); 
	//2. 对 1 进行优化,让x的父元素直接等于其父元素的父元素,下次查找时则可减少查找次数 
	return f[x] == x ? x : f[x] = Find(f[x]);
}

void Union(ll x, ll y) {
	int fx = Find(x);
	int fy = Find(y);
	//如果两个元素的“最年老”父元素不相等则他们不在同一个集合,对他们进行合并 
	if(fx != fy) {
		f[fy] = fx;//注意这里直接改变fy的值 
		a[fx] = max(a[fx], a[fy]);
	}
}

int main() {
	int n, m;
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
		f[i] = i;//首先让每个元素的父元素设为自身 
	}
	while(m--) {
		int u, v;
		scanf("%d%d", &u, &v);
		Union(u, v);
	}

	ll ans = 0;
	for(int i = 1; i <= n; i++) {
		ans += a[Find(i)];
	}
	printf("%lld", ans);
	return 0;
}

你可能感兴趣的:(数据结构与算法,数据结构,算法,c++)