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