P2143 [JSOI2010] 鉅額獎金 題解

MoyouSayuki發表於2024-04-02

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;
}

相關文章