2020 杭电多校1 1001 Total Eclipse (并查集)

题目

题目链接

吐槽

2020 杭电多校1 1001 Total Eclipse (并查集)_第1张图片
2020 杭电多校1 1001 Total Eclipse (并查集)_第2张图片
2020 杭电多校1 1001 Total Eclipse (并查集)_第3张图片
偷偷改题目没提示
2020 杭电多校1 1001 Total Eclipse (并查集)_第4张图片
2群也没通知
2020 杭电多校1 1001 Total Eclipse (并查集)_第5张图片
好大的一口锅,把我们队伍整自闭了。

思路

朴素的想法,每次我都挑选权值最小的点,然后将它所在的连通块的所有点都减去该权值,该点即变为0,与该点相连的边都删掉,这个过程可能会产生新的连通块,不断地重复删点的操作,直到所有的点的权值都为0。

但是这么做会T,正难则反,反过来进行代码实现。

由 在整个图上每次选最小权值删点 转换为 在空图上每次选最大权值加点。

问题在于怎么加

我们知道,删掉一个点意味着与它连接的连通块都减去相应的值,反过来则对应,加入一个点则看与它相连的点,如果这些点已经加入集合中,则将他们的父亲更新为当前点,最后计算所有点与父亲结点的差值的和就是答案。其实就是删边的逆操作,这样我们能获得一颗有根树。

代码

#include 
#include 
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;
}

你可能感兴趣的:(2020 杭电多校1 1001 Total Eclipse (并查集))