题目描述
在一个幼儿园里面有 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<iostream> #include<cstdio> #include<algorithm> #include<cmath> #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; }