给出一个并查集连边的过程,问每次连边之前,选出四个都不在同一集合内的选法。
垃圾文科生又被组合数上了一课。
首先这题直接算根本算不动,容易得知一条边都没有的时候选法是Cn4,所以可以考虑去算每次连边的损失。
那么每次连边损失的选法就是这两个集合同时选出人的情形。
怎么算呢?首先,这两个集合(k1,k2)各选一人,选法是集合大小相乘。之后要从剩下的集合里选出2人,如果不考虑不能从同一集合内选,选法是C(n - k1 - k2)2,那么只要把这个组合数内所有在同一集合内的选法都减掉,就是剩下的选法。这个值是可以动态维护的,两个集合合并,只需要把这两个集合的共享减掉,再加上新集合的共享即可。
#include <bits/stdc++.h>
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;
}