P2143 [JSOI2010] 巨额奖金 题解

P2143 [JSOI2010] 巨额奖金 题解

矩阵树定理+Kruskal

最小生成树计数。

思路

MST 都是喵喵题。

引理 1:所有合法的权值相同边的连边方案,得到的连通块情况是相同的。

感性理解:如果不相同意味着至少有一条边可以连通一对连通块。

所以我们可以这么做:先跑 Kruskal 标记树边,然后枚举边权,对于同一边权的树边先删掉,所有剩余的图用并查集缩点,之后加入当前边权的边,缩点后图的生成树个数就是当前边权边的放置方案,根据引理,每个边权贡献独立,所以乘法原理即可。

注意每次要把所有的非当前边权的树边加入,否则可能导致图不连通。

时间复杂度:\(O(n^3)\)

// Problem: P4208 [JSOI2008] 最小生成树计数
// Author: Moyou
// Copyright (c) 2024 Moyou All rights reserved.
// Date: 2024-04-02 13:29:52

#include <algorithm>
#include <iostream>
#include <map>
#define int long long
using namespace std;
const int N = 200 + 10, mod = 31011;

int n, m, lap[N][N], fa[N];
struct qwq {
    int u, v, w;
} e[1010];
int find(int x) {return fa[x] == x ? x : fa[x] = find(fa[x]);}
void merge(int a, int b) {
    int x = find(a), y = find(b);
    if(x == y) return ;
    fa[x] = y;
}
int tot;
map<int, int> h;
int id(int x) {
    if(h.count(x)) return h[x];
    return h[x] = ++ tot;
}
void add(int a, int b) {
    lap[a][a] ++, lap[b][b] ++, lap[a][b] --, lap[b][a] --;
}
int det(int n) {
    int c = 1;
    for(int i = 1; i < n; i ++)
        for(int j = i + 1; j < n; j ++)
            while(lap[j][i]) {
                int d = lap[i][i] / lap[j][i];
                for(int k = i; k < n && d; k ++)
                    lap[i][k] = (lap[i][k] - 1ll * d * lap[j][k] % mod) % mod;
                swap(lap[i], lap[j]), c = -c;
            }
    for(int i = 1; i < n; i ++) c = 1ll * c * lap[i][i] % mod;
    return (c + mod) % mod;
}
void clear() {
    for(int i = 1; i <= tot; i ++)
        for(int j = 1; j <= tot; j ++)
            lap[i][j] = 0;
    tot = 0; h.clear();
}
bool ok[1010];
void kruskal() {
    for(int i = 1; i <= n; i ++) fa[i] = i;
    for(int i = 1; i <= m; i ++) {
        int x = find(e[i].u), y = find(e[i].v);
        if(x != y) fa[x] = y, ok[i] = 1;
    }
}

signed main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= m; i ++)
        cin >> e[i].u >> e[i].v >> e[i].w;
    sort(e + 1, e + m + 1, [](qwq a, qwq b) {return a.w < b.w;});
    int ans = 1;
    kruskal();
    for(int i = 1, j = 0; i <= m; i = j + 1) {
        clear();
        for(int i = 1; i <= n; i ++) fa[i] = i;
        j = i;
        while(j < m && e[j + 1].w == e[i].w) j ++;
        for(int k = 1; k < i; k ++) if(ok[k]) merge(e[k].u, e[k].v);
        for(int k = j + 1; k <= m; k ++) if(ok[k]) merge(e[k].u, e[k].v);
        for(int k = i; k <= j; k ++) {
            int x = find(e[k].u), y = find(e[k].v);
            if(x != y) add(id(x), id(y));
        }
        for(int k = i; k <= j; k ++)
            merge(e[k].u, e[k].v);
        ans = 1ll * ans * det(tot) % mod;
    }
    for(int i = 1; i < n; i ++) if(find(i) != find(i + 1)) return cout << 0, 0;
    cout << ans << '\n';

    return 0;
}

posted @ 2024-04-02 15:17  MoyouSayuki  阅读(13)  评论(0编辑  收藏  举报
:name :name