Atcoder Nikki Qual E Bridge

题意

给一张无向图,点带权,边也带权

要求在图中删去最少的边,使得对于每一条边,它所在连通块的点权之和大于其边权


解法

首先,看到题目里出现了删边,我们首先想到反向加边(如星球大战

那么最后的答案就是\(m-ans\)\(ans\)为加入的边

考虑一个边权为\(w\)的边\(E(u,v)\)

它是合法的,当且仅当\(u\)所在连通块与\(v\)所在连通块的点权之和大于\(w\)

我们思考一下,如果它是合法的,将会给答案带来什么影响

我们能够发现,边权\(\leq w\)的边,如果能与\(E\)在同一连通块中,那么它们都会因为\(E\)的加入而变得合法(无论它们之前合法与否)

那么这就给了我们启示:我们应该从小到大加边

对于一个在当前不合法的边,我们不能直接舍弃,因为它有可能因为后来加入的某些边而变得合法

因此我们开一个数组\(cnt\),用来记录某个连通块内目前不合法的边的数量

当我们遇到一条当前合法的边,我们把它加入答案,并且把其连接的两个连通块内的\(cnt\)数组也一并计入答案,因为此时它们因为这条边而变得合法了

维护连通块的操作用并查集实现,维护连通性与点权和,代码也很简单


代码

#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;

struct edge {
	int u, v, w;
	bool operator < (const edge &x) const {
		return w < x.w;	
	}
} e[N]; 

int n, m, ans;
int sum[N], cnt[N];

int id[N];

inline int get(int x) {
	return x == id[x] ? x : id[x] = get(id[x]);	
}

int main() {
	
	scanf("%d%d", &n, &m);
	
	for (int i = 1; i <= n; ++i)	
		scanf("%d", sum + i);
	for (int i = 1; i <= m; ++i)
		scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
	
	sort(e + 1, e + m + 1);
	
	for (int i = 1; i <= n; ++i)	id[i] = i;
	
	for (int i = 1; i <= m; ++i) {
		int u = e[i].u, v = e[i].v;
		int fu = get(u), fv = get(v);
		if (fu != fv) {
			sum[fu] += sum[fv];
			cnt[fu] += cnt[fv];
			cnt[fv] = sum[fv] = 0;
			id[fv] = fu;	
		}
		cnt[fu]++;
		if (sum[fu] >= e[i].w) {
			ans += cnt[fu];
			cnt[fu] = 0;
		}
	}
	
	printf("%d\n", m - ans);
	
	return 0;
}
posted @ 2019-09-01 21:16  四季夏目天下第一  阅读(157)  评论(0编辑  收藏  举报