题意
给一张无向图,点带权,边也带权
要求在图中删去最少的边,使得对于每一条边,它所在连通块的点权之和大于其边权
解法
首先,看到题目里出现了删边,我们首先想到反向加边(如星球大战)
那么最后的答案就是(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
", m - ans);
return 0;
}