Contents

2019牛客多校9E题详解_算法日常[11/100]

组合数学思维题

题目链接

2019牛客多校9 E题

https://cdn.jsdelivr.net/gh/wolfdan666/BlogPic/%E7%AE%97%E6%B3%95/2019%E5%B9%B4%E5%A4%9A%E6%A0%A1/%E7%89%9B%E5%AE%A2/%E7%AC%AC%E4%B9%9D%E5%9C%BA/E_ti1.png

https://cdn.jsdelivr.net/gh/wolfdan666/BlogPic/%E7%AE%97%E6%B3%95/2019%E5%B9%B4%E5%A4%9A%E6%A0%A1/%E7%89%9B%E5%AE%A2/%E7%AC%AC%E4%B9%9D%E5%9C%BA/E_ti2.png

题解思路

其实解法一二的本质内核是一样的,可以都看一下

解法一

当合并这两个集合的时候,应该将这两个集合合并后消失的贡献减去 消失的贡献就应该是选择了一个a,选择了一个b,从剩下的众多集合中选择两个 (即cd,ce,ef……)那么这个怎么算呢,可以用完全平方公式来推导 (a+b+c+d)^2=a^2+b^2+c^2+d^2+2ab+2ac+2bc+2ad+2bd+2cd 所以众多集合中选择任意选择两个的情况可以用(和的平方-平方的和)/2来求(最重要的一步)

解法二

https://cdn.jsdelivr.net/gh/wolfdan666/BlogPic/%E7%AE%97%E6%B3%95/2019%E5%B9%B4%E5%A4%9A%E6%A0%A1/%E7%89%9B%E5%AE%A2/%E7%AC%AC%E4%B9%9D%E5%9C%BA/E_tijie_ldm.png

AC代码

解法一代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <bits/stdc++.h>

using ll = long long;
const int MAXN = 100010;

int n, m;
int f[MAXN], sz[MAXN];
ll sum;

inline ll sqr(int x) {
    return 1ll * x * x;
}

inline int getf(int x) {
    return f[x] == x ? x : (f[x] = getf(f[x]));
}

int main() {
    scanf("%d%d", &n, &m);
    ll ans = (__int128) n * (n - 1) * (n - 2) * (n - 3) / 24;
    printf("%lld\n", ans);
    for (int i = 1; i <= n; ++i) {
        f[i] = i;
        sz[i] = 1;
    }
    /*最开始的平方和*/
    sum = n;
    while (m--) {
        int u, v;
        scanf("%d%d", &u, &v);
        u = getf(u);
        v = getf(v);
        if(ans==0 || u==v) goto END;
        if (u != v) {
            /*减掉合并部分的平方和*/
            sum -= sqr(sz[u]) + sqr(sz[v]);
            /*后面的(sqr(n - sz[u] - sz[v]) - sum) / 2;就是`和的平方`-`平方和`=`剩下的所有两两组合`*/
            ll tmp = 1ll * sz[u] * sz[v] * (sqr(n - sz[u] - sz[v]) - sum) / 2;
            f[u] = v;
            sz[v] += sz[u];
            /*新的平方和的维护*/
            sum += sqr(sz[v]);
            /*减去合并减少的贡献值*/
            ans -= tmp;
        }
        END:
        printf("%lld\n", ans);
    }
    return 0;
}

解法二代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int maxn = 100000 + 5;

ll C[maxn][5];
int p[maxn], sz[maxn], cnt[maxn];
int n, m;
ll ans;
set<int> st;

inline void init() {
    for(int i = 0; i < maxn; i++) C[i][0] = 1;
    for(int i = 1; i < maxn; i++) {
        for(int j = 1; j < 5; j++) {
            C[i][j] = C[i-1][j] + C[i-1][j-1];
        }
    }
}

inline int Find(int x) { return x == p[x] ? x : p[x] = Find(p[x]); }

inline void Union(int x, int y) {
    int fx = Find(x), fy = Find(y);
    if(fx != fy) {
        if(fx > fy) swap(fx, fy);
        p[fx] = fy;
        ll tp = C[n-sz[fx]-sz[fy]][2];
        for(auto i : st) {
            tp -= C[i][2]*cnt[i];
        }
        tp += C[sz[fx]][2]+C[sz[fy]][2];
        if(tp > 0) ans -= 1LL*sz[fx]*sz[fy]*tp;
        cnt[sz[fx]]--;
        cnt[sz[fy]]--;
        if(cnt[sz[fx]] == 0) st.erase(sz[fx]);
        if(cnt[sz[fy]] == 0) st.erase(sz[fy]);
        sz[fy] += sz[fx];
        cnt[sz[fy]]++;
        st.insert(sz[fy]);
    }
}


int main() {
    init();
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        p[i] = i;
        sz[i] = 1;
    }
    cnt[1] = n; st.insert(1);
    ans = C[n][4];
    printf("%lld\n", ans);
    while(m--) {
        int u, v;
        scanf("%d%d", &u, &v);
        Union(u, v);
        printf("%lld\n", ans);
    }

    return 0;
}

每天一句叨叨

生活总是很奇妙,我们到底该去向何方?

世俗的成功吗?还是当下的快乐呢?

珍惜身边的人呢?还是继续寻找?