[2020HDU多校]Total Eclipse 并查集

题意:

n个点的图,每个点有点权,每次选取一个联通分量,然后其中每个点点权-1,点权不能为负,求最少需要操作的次数

分析:

一开始用dp搞的,然后一直wa,其实这道题是并查集,看到连通分量应该想到的,还是题做少了。
因为这个题是从小到大删的点,我们可以逆向考虑,点权从大到小加入点,每加入一个点u,贡献就是(u所连接的连通分量的个数 - 1) * u的点权。

代码:

#include
#define mem(a, b) memset(a, b, sizeof(a))
#define max(a,b) (a>b?a:b)
typedef long long ll;
using namespace std;

const int maxn = 1e5 + 10;
const int maxm = 2e5 + 10;

struct Edge {
    int to, next;
}E[maxm << 1];

int tot, head[maxn], v[maxn];

void addEdge(int from, int to, bool istwo = false) {
    E[tot] = Edge{ to,head[from] };
    head[from] = tot++;

    //双向边
    if (istwo) {
        E[tot] = Edge{ from,head[to] };
        head[to] = tot++;
    }
}
ll ans;
int vis[maxn], r[maxn];
int T, n, m, c;

int fa[maxn];//并查集
int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

void merge(int a, int b) {
    a = find(a);
    b = find(b);
    if (a != b) fa[a] = b;
}


void solve() {
    mem(vis, 0);
    ans = 0;
    sort(r + 1, r + 1 + n, [](int a, int b) {
        return v[a] > v[b];
        });

    for (int i = 1; i <= n; i++) {
        int now = r[i];
        ans += v[now];
        int Mxx = -1;
        for (int j = head[now]; j != -1; j = E[j].next) {
            int to = E[j].to;
            if (vis[to]) {
                if (find(to) != find(now)) {
                    merge(to, now);
                    ans -= v[now];
                }
            }
        }
        vis[now] = 1;
    }

}
int main() {
    scanf("%d", &T);
    for (c = 1; c <= T; c++) {
        scanf("%d%d", &n, &m);
        tot = 0;
        memset(head, -1, sizeof(int) * (n + 2));
        for (int i = 1; i <= n; i++) {
            scanf("%d", v + i);
            r[i] = i; fa[i] = i;
        }
        for (int i = 0; i < m; i++) {
            int f, t;
            scanf("%d%d", &f, &t);
            addEdge(f, t, true);
        }
        solve();
        printf("%lld\n", ans);

    }
}

你可能感兴趣的:(2020HDU多校训练)