题目
吐槽
偷偷改题目没提示
2群也没通知
好大的一口锅,把我们队伍整自闭了。
思路
朴素的想法,每次我都挑选权值最小的点,然后将它所在的连通块的所有点都减去该权值,该点即变为0,与该点相连的边都删掉,这个过程可能会产生新的连通块,不断地重复删点的操作,直到所有的点的权值都为0。
但是这么做会T,正难则反,反过来进行代码实现。
由 在整个图上每次选最小权值删点 转换为 在空图上每次选最大权值加点。
问题在于怎么加
我们知道,删掉一个点意味着与它连接的连通块都减去相应的值,反过来则对应,加入一个点则看与它相连的点,如果这些点已经加入集合中,则将他们的父亲更新为当前点,最后计算所有点与父亲结点的差值的和就是答案。其实就是删边的逆操作,这样我们能获得一颗有根树。
代码
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
vector <int> G[maxn];
ll sum;
int a[maxn], f[maxn], r[maxn];
bool visit[maxn];
int find(int x){
return x == f[x] ? x : f[x] = find(f[x]);
}
bool cmp(int x, int y){
return a[x] > a[y];
}
int main(){
int t; scanf("%d", &t);
while(t--){
sum = 0;
int n, m; scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
f[i] = r[i] = i, visit[i] = 0;
G[i].clear();
}
sort(r + 1, r + 1 + n, cmp);
for(int j = 1, u, v; j <= m; j++){
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
for(int i = 1; i <= n; i++){
int u = r[i];
sum += a[u];
visit[u] = 1;
for(int j = 0; j < G[u].size(); j++){
int v = G[u][j];
if(visit[v] && find(u) != find(v)){
sum -= a[u];
f[find(v)] = find(u);
}
}
}
printf("%lld\n", sum);
}
return 0;
}