给定一个无向图,求其最小生成树个数。

题解

尝试证明一个奇奇怪怪的定理:一个无向图的所有最小生成树中某种权值的边的数目相同。

按照 kruskal 算法的流程,我们会在对边排序之后尝试从小到大尝试加入某种权值的所有边,我们设权值最小的边的权值为 w w ,原图中权值为 w w 的边有 c c 条,出现在最小生成树中的权值为 w w 的边有 a a 条。那么任何一个加入 a a 条边权为 w w 的边而不使图成环的方案都可以使原图到达相同的连通性。

假设加入能够加入的 a a 条边以后得到若干个连通块,记其中一个连通块为 G G ,其余连通块同理,那么去掉 G G 中一条边,得到两个连通块 A,B A, B ,现在尝试添加一条不同于原来在 G G 中任意一条边的边,那么此时这条边 (u,v) (u, v) 有两种情况:

  1. uA,vB u \in A, v \in B ,那么添加这条边后仍然得到连通块 G G
  2. (uA,vA)(uB,vB) (u \in A, v \in A) \wedge (u \in B, v \in B) 那么添加这条边势必成环,此时不允许添加这条边。

在添加第二小的边的时候,将添加最小的边之后得到的若干个连通块缩点,这时已经没有可以加进最小生成树的权值最小的边,所以权值最第二小的边变成新的权值最小的边,此时尝试加边之后得到的图的连通性的性质同上述情况。

以添加进最小生成树的边的权值划分阶段,那么每个阶段中添加的边的数量是固定的,这也就意味着某种权值的边的数目是完全相同的。

以上内容借鉴如下博客。。 BZOJ1016 [JSOI2008]最小生成树计数

贴上 sengxian 更加严谨的证明: BZOJ 1016 - [JSOI2008]最小生成树计数

得到了这个结论以后,就可以枚举选择每种边权的边了,如果在节点间连上枚举到的边不成环,那么这就是一种可行的最小生成树方案。利用状压枚举,统计每种边权的方案数,最后乘法原理即可。

代码

#include <cstdio>
#include <cstring>
#include <climits>
#include <map>
#include <vector>
#include <algorithm>
const int MAX_N = 100;
const int MAX_M = 1000;
const int MOD = 31011;
struct Edge {
int u, v, w;
bool vis;
bool operator<(const Edge &other) const {
return w < other.w;
}
} E[MAX_M + 1];
struct UFS {
int fa[MAX_N + 1];
void init(int n) {
for (int i = 0; i <= n; i++) fa[i] = i;
}
int find(int x) {
return x == fa[x] ? fa[x] : fa[x] = find(fa[x]);
}
void merge(int x, int y) {
int f1 = find(x), f2 = find(y);
fa[f1] = f2;
}
bool check(int x, int y) {
return find(x) == find(y);
}
} ufs;
struct EdgeGroup {
int cnt;
std::vector<Edge> e;
};
int n, m;
bool g[MAX_N + 1][MAX_N + 1];
std::map<int, EdgeGroup> groups;
inline bool kruskal() {
std::sort(E, E + m);
ufs.init(n);
int cnt = 0;
for (int i = 0; i < m; i++) {
if (!ufs.check(E[i].u, E[i].v)) {
E[i].vis = true;
ufs.merge(E[i].u, E[i].v);
groups[E[i].w].cnt++;
cnt++;
}
groups[E[i].w].e.push_back(E[i]);
}
return cnt == n - 1;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 0; i < m; i++) {
scanf("%d %d %d", &E[i].u, &E[i].v, &E[i].w);
}
if (!kruskal()) {
puts("0");
} else {
long long ans = 1;
for (int i = 0; i < m; i++) if (E[i].vis) g[E[i].u][E[i].v] = g[E[i].v][E[i].u] = true;
for (std::map<int, EdgeGroup>::const_iterator it = groups.begin(); it != groups.end(); it++) {
if (it->second.cnt == 0) continue;
int t = 0;
for (unsigned int s = 1; s < (1 << it->second.e.size()); s++) {
int tot = 0;
for (unsigned int i = 0; i < it->second.e.size(); i++) if (s & (1 << i)) tot++;
if (tot != it->second.cnt) continue;
for (std::vector<Edge>::const_iterator e = it->second.e.begin(); e != it->second.e.end(); e++) {
g[e->u][e->v] = g[e->v][e->u] = false;
}
for (unsigned int i = 0; i < it->second.e.size(); i++) {
if (s & (1 << i)) g[it->second.e[i].u][it->second.e[i].v] = g[it->second.e[i].v][it->second.e[i].u] = true;
}
ufs.init(n);
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (g[i][j]) {
if (ufs.check(i, j)) goto Continue;
ufs.merge(i, j);
}
}
}
t++;
Continue:;
}
// printf("t = %d\n", t);
(ans *= t) %= MOD;
for (std::vector<Edge>::const_iterator e = it->second.e.begin(); e != it->second.e.end(); e++) {
g[e->u][e->v] = g[e->v][e->u] = e->vis;
}
}
printf("%lld\n", ans);
}
return 0;
}