【JSOI 2008】 最小生成树计数
【题目链接】
【算法】
笔者做这题参考了这篇博客 :
https://blog.sengxian.com/solutions/bzoj-1016
推荐阅读
首先,我们需要知道三个定理 :
定理1 : 若A,B是两棵不同的最小生成树,它们的权值从小到大排列分别为 :
W(a1),W(a2),W(a3)....W(an-1)
W(b1),W(b2),W(b3)....W(bn-1)
那么,对于任意的i,都有W(ai) = W(bi)
定理2 : 当最小生成树中所有w <= w0的边被加入后,图的联通性唯一
定理3 : 若A是一棵最小生成树,A中权值为v的边有k条,那么,用任意k条权值为v的边替换A中权值为v的边且不产生
环的方案都是一棵最小生成树
证明详见笔者推荐的那篇博客
有了这三个定理,这题就很好做啦! 首先,任意求一棵最小生成树,记录每种权值的边出现的次数,然后,对每种
权值的边做一遍深度优先搜索DFS,求出方案数,然后乘法原理,即可
【代码】
注意因为进行DFS时需要回溯,所以,并查集不能路径压缩
#include<bits/stdc++.h> using namespace std; #define MAXN 110 #define MAXM 1010 const int MOD = 31011; int n,m,i,j,ans = 1,pos,len,sum,sx,sy; int fa[MAXN],s[MAXN],val[MAXN],l[MAXN],r[MAXN]; struct Edge { int u,v,w; } e[MAXM]; inline void init(int n) { int i; for (i = 1; i <= n; i++) fa[i] = i; } int get_root(int x) { if (fa[x] == x) return x; return get_root(fa[x]); } bool cmp(Edge a,Edge b) { return a.w < b.w; } bool kruskal() { int i,cnt = 0,sx,sy; for (i = 1; i <= m; i++) { sx = get_root(e[i].u); sy = get_root(e[i].v); if (e[i].w == val[len]) r[len]++; if (sx != sy) { fa[sx] = sy; cnt++; if (e[i].w == val[len]) s[len]++; else { len++; l[len] = r[len] = i; s[len]++; val[len] = e[i].w; } } } return cnt == n - 1; } inline void dfs(int now,int r,int c) { int sx,sy; if (now > r) { if (c == s[pos]) sum++; return; } dfs(now+1,r,c); sx = get_root(e[now].u); sy = get_root(e[now].v); if (sx != sy) { fa[sx] = sy; dfs(now+1,r,c+1); fa[sx] = sx; } } int main() { scanf("%d%d",&n,&m); for (i = 1; i <= m; i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w); sort(e+1,e+m+1,cmp); init(n); if (!kruskal()) { puts("0"); return 0; } init(n); for (i = 1; i <= len; i++) { sum = 0; pos = i; dfs(l[i],r[i],0); ans = (ans * sum) % MOD; for (j = l[i]; j <= r[i]; j++) { sx = get_root(e[j].u); sy = get_root(e[j].v); if (sx != sy) fa[sx] = sy; } } printf("%d\n",ans); return 0; }