Author: lllyouo
Date: 20250225
tag: 最小生成树计数
link: https://www.luogu.com.cn/problem/P4208问题描述
分析
如果给定图 Kruskal 算法执行过程可以得到如下性质:
的边权从小到大为 , 的边权从小到大为 必有 。 - 若
和 都从零由小到大逐步加边,每种权值的边加完后图的连通性相同。 - 假设在最小生成树
种,权值为 的边有 条,用任意 条权值为 的边替换 中权值为 的边且不产生环的最小生成树都是一颗合法的最小生成树。
我们可以将给定图
具体来说,首选通过 kruskal 算法计算给定图的最小生成树
如何求解 kruskal 算法执行前,图中边的权值已经从小到大排序完毕,即权值相同的边集中在一个区域,将边按照权值大小分成
参考代码
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = 1010, MOD = 31011;
struct Edge {
int u, v, w;
bool operator< (const Edge& x) {
return w < x.w;
}
} e[M];
struct Node {
// l 相同边权的边的起点序号
// r 相同边权的边的终点序号
// cnt 选入生成树的某权值的边在生成树中的数量
int l, r, cnt;
} ed[1010];
int n, m, ans, num, cnt, p[110];
// 不进行路径压缩
int find(int x) {
if (x == p[x]) return x;
return find(p[x]);
}
void dfs(int x, int now, int k) {
if (now == ed[x].r + 1) {
if (k == ed[x].cnt) cnt++; // 确保与生成树所需边数相同
return ;
}
int fu = find(e[now].u);
int fv = find(e[now].v);
if (fu != fv) { // 加入该边不构成环
p[fu] = fv;
dfs(x, now + 1, k + 1); // 选该边
p[fu] = fu; // 回溯
}
dfs(x, now + 1, k); // 不选该边
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) p[i] = i;
for (int i = 1; i <= m; i++) {
int a, b, c; cin >> a >> b >> c;
e[i] = {a, b, c};
}
sort(e + 1, e + 1 + m);
int tot = 0; // 生成树中边的数量
for (int i = 1; i <= m; i++) {
if (e[i].w != e[i - 1].w) {
num++;
ed[num].l = i;
ed[num - 1].r = i - 1;
}
int fu = find(e[i].u);
int fv = find(e[i].v);
if (fu != fv) {
p[fu] = fv;
ed[num].cnt++;
tot++;
}
}
if (tot != n - 1) {
cout << 0 << endl;
return 0;
}
ed[num].r = m;
ans = 1;
for (int i = 1; i <= n; i++) p[i] = i;
for (int i = 1; i <= num; i++) {
cnt = 0;
dfs(i, ed[i].l, 0);
ans = (ans * cnt) % MOD;
for (int j = ed[i].l; j <= ed[i].r; j++) {
int fu = find(e[j].u);
int fv = find(e[j].v);
if (fu != fv) p[fu] = fv;
}
}
cout << ans << endl;
return 0;
}