题意:有n个点,m条边,每次可以把m条边里面选一个放进入图中,然后每次可以使极大联通子图的权重+1,求最少权重+1的操作次数
分析:
由于min(5*n,m),那么所有边一定能够放进入图中,限制条件没有用
限制条件:
- 每次只能同时操作一个集合内的点
- 最后的所有需求都要减少为0
由于每次只能同时操作一个集合内的点,那么可以想到并查集的思想,用并查集维护
100. 增减序列(差分+贪心)_qq12323qweeqwe的博客-CSDN博客
并查集扩展域:值域,维护一个集合共同的值
由增减序列的思想可以知道,这题只可以对一个集合中的元素进行-1操作,也就是说
当出现 1,0,1 时,集合不能再继续操作,那么也是从增减序列的题目中延伸,可以想到先让集合内的所有点都变到同样高度,然后再同时让权重-1,就是最小操作次数了
由于只有+1操作,如图,应该先让中间高的先-1到集合内所有点一样高,然后再让整个集合内的点一起--
在集合内要完成这种操作,就是先让一个集合维护权重大的点,然后不断加入权重小的点,实现并查集
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 5e6 + 10;
typedef long long ll;
int a[N], p[N];
struct node
{
int l, r;
int w;
bool operator <(const node b)const
{
return w > b.w;
}
}le[N];
int find(int x)
{
if (x != p[x])
p[x] = find(p[x]);
return p[x];
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1;i <= n;i++)
cin >> a[i], p[i] = i;
for (int i = 1;i <= m;i++)
{
cin >> le[i].l >> le[i].r;
//确保能够先处理需要高的集合元素
le[i].w = min(a[le[i].l], a[le[i].r]);
}
ll res = 0;
//先处理高需求
sort(le + 1, le + 1 + m);
for (int i = 1;i <= m;i++)
{
int pa = find(le[i].l), pb = find(le[i].r);
if (pa == pb) continue;
//并查集操作
res += abs(a[pa] - a[pb]);
p[pb] = pa;
//维护并查集的值域,并查集集合内所有元素=根结点的值
a[pa] = a[pb] = min(a[pa], a[pb]);
}
for (int i = 1;i <= n;i++)
if (find(i)== i) res += a[i];
cout << res;
return 0;
}