2019牛客多校E.All men are brothers(并查集+排列组合)

给出一个并查集连边的过程,问每次连边之前,选出四个都不在同一集合内的选法。

垃圾文科生又被组合数上了一课。

首先这题直接算根本算不动,容易得知一条边都没有的时候选法是Cn4,所以可以考虑去算每次连边的损失。

那么每次连边损失的选法就是这两个集合同时选出人的情形。

怎么算呢?首先,这两个集合(k1,k2)各选一人,选法是集合大小相乘。之后要从剩下的集合里选出2人,如果不考虑不能从同一集合内选,选法是C(n - k1 - k2)2,那么只要把这个组合数内所有在同一集合内的选法都减掉,就是剩下的选法。这个值是可以动态维护的,两个集合合并,只需要把这两个集合的共享减掉,再加上新集合的共享即可。

#include 

using namespace std;
typedef unsigned long long ll;

const int maxn = 1e5 + 5;

int n, m;
int f[maxn];
ll sz[maxn];

ll ans, sum;

ll C2(ll x) {
    if (x <= 1) {
        return 0;
    }
    return x * (x - 1) / 2;
}

void init() {
    for (int i = 1; i <= n; ++i) {
        f[i] = i, sz[i] = 1;
    }
}

int find(int x) {
    if (x == f[x]) {
        return x;
    }
    return f[x] = find(f[x]);
}

void merge(int x, int y) {
    x = find(x), y = find(y);
    if (x != y) {
        int a = sz[x], b = sz[y];
        ans = ans - (sz[x] * sz[y]) * (C2(n - sz[x] - sz[y]) - sum + C2(sz[x]) + C2(sz[y]));
        sum = sum - C2(sz[x]) - C2(sz[y]);
        sum = sum + C2(sz[x] + sz[y]);
        f[y] = x;
        sz[x] += sz[y];
    }
}

int main() {
    scanf("%d%d", &n, &m);
    init();
    ans = 1ULL * n * (n - 1) / 2 * (n - 2) / 3 * (n - 3) / 4;
    printf("%llu\n", ans);
    int a, b;
    while (m--) {
        scanf("%d%d", &a, &b);
        merge(a, b);
        printf("%llu\n", ans);
    }
    return 0;
}

你可能感兴趣的:(ACM)