2022牛客寒假算法基础集训营2:B-小沙的魔法(逆向思维、最大生成树、贪心)

小沙的魔法

在这里插入图片描述
题意:

每个点都有一个目标值,初始权值为0,每次 操作1 使一个连通块(内所有点)权值++,操作2 选择一条边连接两个连通块,问 使得每个点权值上升成目标值所需要的最少 操作1 次数

思路:

反向思考

  • 我们假设目标已完成,每个点都到达了自己的目标值,反过来每次操作使权值减1,使问题转化成 使得每个点权值变为0(不能为负)所需要的最少 操作1 次数

由点到面

  • :一条边可以连接两个点,在此之后一次 操作1 就能使这两个点同时减 1,显然多产生这种连接可以减少 最少操作次数
  • :整体上看连接可以省去某个点的部分操作数,贪心地想我们应该优先连接两个权值大(目标值)的点,可以减少更多操作数

例:

在这里插入图片描述

经过排序后,显然我们最先拿到的边是 { 90 − 70 90-70 9070 } ,它们相连的必要条件是——90 的权值下降到 70,也就是先经过 20次 操作1,当两点权值相同时再连边,才能保证权值同时下降、任意点权值不为负;

之后拿到的边是 { 70 − 60 70-60 7060 },同理 70 降到 60,再合并连通块;

最后我们最少的 操作1 次数就为 { 20 + 10 + 最 后 连 通 块 的 权 值 ( 60 ) 20 + 10 + 最后连通块的权值(60) 20+10+60 } = = 90 == 90 ==90

连通块内点的权值显然最后都相同,点越多,我们节省的操作次数就越多

在这里插入图片描述

60 换成 80 也是一个道理,一条边的两点最后会依附到权值小的那个点上(只能通过操作把大变小)

在这里插入图片描述
先连 {20—80}: a n s = 60 + 70 + 20 = 150 ans= 60 + 70 + 20= 150 ans=60+70+20=150
先连 {90—80}: a n s = 10 + 60 + 20 = 90 ans= 10 + 60 + 20= 90 ans=10+60+20=90

————————————————————————————————————————

C o d e : Code: Code:

#include<bits/stdc++.h>
#include<unordered_set>
#include<unordered_map>
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))
#define sca scanf
#define pri printf
#define ul (u << 1)
#define ur (u << 1 | 1)
//#define x first
//#define y second
//#pragma GCC optimize(2)
//[博客地址](https://blog.csdn.net/weixin_51797626?t=1) 
using namespace std;

typedef long long ll;
typedef pair<int, int> PII;
typedef unsigned long long ull;

const int N = 500010, M = 5000010, MM = N;
int INF = 0x3f3f3f3f, mod = 1e9 + 7;
ll LNF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, T, S, D;
int p[N], d[N];
struct edge
{
	int a, b, w;
	bool operator<(const edge& no)const { return w > no.w; }
}ed[M];

int find(int x) {
	if (p[x] != x)p[x] = find(p[x]);
	return p[x];
}

int main() {
	cinios;
	
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> d[i];//目标值
		p[i] = i;//初始化并查集
	}

	for (int i = 0; i < m; i++) {
		int a, b;
		cin >> a >> b;
		ed[i] = { a,b,min(d[a],d[b]) };//贪心,尽可能连较大的点先
		//可以自己模拟一下
		//若先连小点,之后大的点要花更多操作降值,才能使权值一致
	}
	sort(ed, ed + m);

	ll ans = 0;
	for (int i = 0; i < m; i++) {
		int a = ed[i].a, b = ed[i].b;
		a = find(a), b = find(b);
		if (a == b)continue;//若此边两点不在同一个连通块
		ans += abs(d[a] - d[b]);//记录降值的操作数
		p[a] = b;
		d[a] = d[b] = min(d[a], d[b]); //依附到小的点上
	}
	
	for (int i = 1; i <= n; i++)
		if (i == find(i))ans += d[i];//最后加上每个连通块的权值(需要减成0)

	cout << ans;

	return 0;
}
/*
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值