牛客多校第九场 E All men are brothers 并查集/组合论
题意:
一开始有n人互不认识,每回合有两个人认识,认识具有传递性,也就是相互认识的人组成小团体。现在问你每个回合,挑选四个人,这四个人互不认识,有多少种挑选方法。
题解:
认识不认识用并查集维护即可,重点在于如何统计挑选方法。
每个回合两个人互相认识,排除两个人本就在一个小团体中的情况,实际上就是两个小团体结合为一个。
那么变得无效化的挑选方法,实际上就是两个人分别来自这两个小团体,剩下两个人来自其他小团体的情况。
从其他集合的所有元素先任取两个,再去掉来自同一集合的两个的情况。
减少的数量等于合并的两个集合大小乘以以上结果。
#include<bits/stdc++.h> using namespace std; typedef long long LL; typedef unsigned long long uLL; typedef long long ll; typedef pair<int, LL>P; const int M = 4e5 * 4 + 5; const LL mod = 1e9 + 7; const LL lINF = 0x3f3f3f3f3f3f3f3f; #define ls (rt<<1) #define rs (rt<<1|1) int n, fa[M], ra[M], m; uLL num[M]; void init(int n) { for (int i = 1; i <= n; i++) { fa[i] = i; ra[i] = 1; num[i] = 1; } } int find(int x) { if (fa[x] == x) return x; else return fa[x] = find(fa[x]); } void unite(int x, int y) { x = find(x); y = find(y); if (ra[x] < ra[y]) { fa[x] = y; num[y] += num[x]; } else { fa[y] = x; num[x] += num[y]; if (ra[x] == ra[y]) ra[x]++; } } uLL ans; uLL sum; int l, r; int main() { scanf("%d%d", &n, &m); ans = (uLL)n *(n - 1)*(n - 2)/6*(n - 3)/4; cout<<ans<<endl; sum = 0; init(n); while (m--) { scanf("%d%d", &l, &r); l = find(l); r = find(r); if (l == r) { cout<<ans<<endl; continue; } else { uLL lst = n - num[l] - num[r]; uLL tmp; tmp = lst * (lst - 1) / 2; tmp = tmp - sum + num[l] * (num[l] - 1) / 2 + num[r] * (num[r] - 1) / 2; tmp = tmp * num[l] * num[r]; ans -= tmp; cout<<ans<<endl; tmp = num[l] * (num[l] - 1) / 2 + num[r] * (num[r] - 1) / 2; sum -= tmp; unite(l, r); l = find(l); // r = find(r); // assert(l==r); sum += num[l] * (num[l] - 1) / 2; } } }