(旧存档) 杭电多校第二场-A题
A 并查集 (正难则反)
题意
给你一个图,上面有点值,你可以选一个联通子图,将上面点的点值减一。问至少要操作多少次,才能使整个图的点值为 0
思路
刚开始肯定是想一些很暴力的手段,假设 G 是一个连通图。
- 每次找到 $G$ 的最小点权 $v.weight$,对应的点为 $v$,答案贡献 $ans += v.weight$
- $G = G - v$,$G$ 每个点的点权减 $v.weight$
- $G$ 为空,算法结束,否则执行
1.
这个方法太暴力了,看上去很不可做。
那么我们不妨把上面的过程倒过来:
- $G’$ 为空,原图为 $G$
- 从 $G$ 中选一个最大点权 $v.weight$,对应的点为 $v$,$G = G - v$,$G’ = G’ + v$
- 记 $G’$ 中的最小点权为 $u.weight$,对应的点为 $u$ , $ans += v.weight - u.weight$,表示使 $G’$ 内的点权一样,至少执行多少次减法
- 若 $G$ 为空,算法结束,否则执行
2.
最后我们将 $G’$ 的点权清零 $ans += min(G)$
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 5, M = 2e5 + 5;
struct edge {int to, next;} edges[M << 1];
int head[N], a[N], tot;
int fa[N], id[N];
bool disable[N];
void init(int n) {
memset(head + 1, -1, sizeof(int) * n);
iota(fa + 1, fa + n + 1, 1);
iota(id + 1, id + n + 1, 1);
memset(disable + 1, true, n);
tot = 0;
}
void add_edge(int u, int v) {
edges[tot] = {v, head[u]};
head[u] = tot++;
}
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
int main() {
int t, n, m;
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
init(n);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
sort(id + 1, id + 1 + n, [](int x, int y) {
return a[x] > a[y];
});
for (int i = 1; i <= m; ++i) {
int u, v;
scanf("%d%d", &u, &v);
add_edge(u, v);
add_edge(v, u);
}
ll ans = 0;
for (int i = 1; i <= n; ++i) {
int u = id[i];
disable[u] = false;
for (int j = head[u]; ~j; j = edges[j].next) {
int v = edges[j].to, fv = find(v);
if (disable[v] || fv == u) continue;
ans += a[fv] - a[u];
fa[fv] = u;
}
}
for (int i = 1; i <= n; ++i)
if (find(i) == i) ans += a[i];
printf("%lld\n", ans);
}
}