2019牛客暑期多校训练营(第九场)All men are brothers——并查集&&组合数

题意

最初有 $n$ 个人且互不认识,接下来 $m$ 行,每行有 $x,y$,表示 $x$ 和 $y$ 交朋友,朋友关系满足自反性和传递性,每次输出当前选取4个人且互不认识的方案数。

分析

并查集维护集合的并。

考虑两个集合的并对答案的影响,总的来说就是减去集合x中选一个、集合y中选一个,剩下的选两个(这两个来自不同集合)。

代码实现上体会 $cs$ 变量的作用。

#include<bits/stdc++.h>
using namespace std;

typedef unsigned long long ll;
const int maxn = 1e5 + 10;
int n, m;
int fa[maxn], rk[maxn];

ll C4(ll x)
{
    return x * (x-1)/2 * (x-2)/3 * (x-3)/4;
}
ll C2(ll x)
{
    return x * (x-1) / 2;
}
void init()
{
    for(int i = 1;i <= n;i++)
    {
        fa[i] = i;
        rk[i] = 1;
    }
}
int find(int x)
{
    if(x != fa[x])  fa[x] = find(fa[x]);
    return fa[x];
}
void unite(int x, int y)
{
    int rx = find(x);
    int ry = find(y);
    if(rx == ry)  return;
    fa[rx] = ry;
    rk[ry] += rk[rx];
}

int main()
{
    scanf("%d%d", &n, &m);
    ll ans = C4(n);
    printf("%lld\n", ans);

    init();

    ll cs = 0;  //每个集合中取两个的方案数的和
    for(int i = 0;i < m;i++)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        if(find(a) == find(b))
        {
            printf("%lld\n", ans);
            continue;
        }
        int rka = rk[find(a)], rkb = rk[find(b)];
        ll tmp1 = 1LL * rka * rkb;
        ll tmp2 = C2(n-rka-rkb) - (cs - (C2(rka)+C2(rkb)));
        cs = cs - (C2(rka)+C2(rkb)) + C2(rka+rkb);
        ans -= tmp1 * tmp2;
        printf("%lld\n", ans);
        unite(a, b);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/lfri/p/11361058.html