码迷,mamicode.com
首页 > Web开发 > 详细

bzoj1016 [JSOI2008]最小生成树计数

时间:2017-03-18 19:10:26      阅读:290      评论:0      收藏:0      [点我收藏+]

标签:return   没有   之间   work   str   while   存在   ==   路径   

Description

现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生成树可能很多,所以你只需要输出方案数对31011的模就可以了。

Input

第一行包含两个数,n和m,其中1<=n<=100; 1<=m<=1000; 表示该无向图的节点数和边数。每个节点用1~n的整数编号。接下来的m行,每行包含两个整数:a, b, c,表示节点a, b之间的边的权值为c,其中1<=c<=1,000,000,000。数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过10条。

Output

输出不同的最小生成树有多少个。你只需要输出数量对31011的模就可以了。

Sample Input

4 6
1 2 1
1 3 1
1 4 1
2 3 2
2 4 1
3 4 1

Sample Output

8

 

正解:$kruskal$+搜索/矩阵树定理。

这道题涉及到一些定理:

定理一:如果 $A, B$ 同为 $G$ 的最小生成树,且 $A$ 的边权从小到大为 $w(a_1), w(a_2), w(a_3), \cdots w(a_n)$,$B$ 的边权从小到大为 $w(b_1),w(b_2), w(b_3), \cdots w(b_n)$,则有 $w(a_i) = w(b_i)$。
证明:设$A, B$ 第一个不同的边的下标为 $i$,不妨设 $w(a_i) \le w(b_i)$,如果不存在这样的 $i$,无需证明。
情况一:$a_i$ 在 $B$ 中,为 $b_j$。那么显然有 $j>i$(否则 $i$ 不是第一个不同的边),则有 $w(a_i)\le w(b_i) \le w(b_j) = w(a_i)$,所以有 $w(a_i) = w(b_i) = w(b_j)$,所以可以调换 $b_i, b_j$ 的位置,$B$ 的权值排列不会改变, $A$ 与 $B$ 这样前 $i$ 条边均为相等,可以递归下去证明。
情况二:$a_i$ 不在 $B$ 中。考虑将 $a_i$ 加入 $B$,则形成了一个环,环中的权值 $v\le w(a_i)$(否则 $B$ 不是最小生成树),且一定有一条边 $b_j$ 不在 $B$ 中,此时仍有 $j>i$(否则 $i$ 不是第一个不同的边),所以有 $w(a_i)\le w(b_i) \le w(b_j) \le w(a_i)$,所以仍有 $w(a_i) = w(b_i) = w(b_j)$,可以将 $b_j$ 替换为 $a_i$,$B$ 的权值排列不会改变且仍为最小生成树,仍然调换 $b_i, b_j$ 的位置,$B$ 的权值排列仍会改变,这样 $A$ 与 $B$ 前 $i$ 条边均为相等,可以递归下去证明。这样换边不会有问题,因为 Kruskal 算法中唯一的状态就是连通性,我们没有改变其连通性,所以是可以递归证明的。

定理二:如果 $A, B$ 同为 $G$ 的最小生成树,如果 $A, B$ 都从零开始从小到大加边($A$ 加 $A$ 的边,$B$ 加 $B$ 的边)的话,每种权值加完后图的联通性相同。
证明:归纳法证明,没有边时显然成立,假设对于权值小于 $v$ 的成立。考虑权值为 $v$ 的边,如果连通性不相同,必然存在 $u, v$ 两点间连通性不同,假设 $A$ 中 $u, v$ 联通,根据 $A, B$ 所有小于 $v$ 的边连通性相同,所以必然存在一条 $u\rightarrow v$ 权值为 $v$ 的边,而根据 Kruskal 算法的执行过程, $B$ 不可能不加这条边,所以两棵最小生成树 $A, B$ 各自权值小于等于 $v$ 的边仍然满足连通性相同。由归纳法可知定理二成立。

定理三:如果在最小生成树 $A$ 中权值为 $v$ 的边有 $k$ 条,用任意 $k$ 条权值为 $v$ 的边替换 $A$ 中的权为 $v$ 的边且**不产生环**的方案都是一棵合法最小生成树。
证明:根据之前的定理,其余的边造成的连通性是定的,权值和也是定的,那么选 $k$ 条不产生环一定能形成一棵树,而且权值与最小生成树的权值一样,故也是最小生成树。

——sengxian

 

于是我们可以先跑一遍最小生成树。然后对于每种权值的边,用搜索或矩阵树定理统计方案数(因为相同边权的边不超过10条所以爆搜能过)。然后乘法原理,直接把每种边的答案相乘就行了。

然而这题如果是搜索并查集不能写路径压缩。。坑了我好久。。

 

 1 //It is made by wfj_2048~
 2 #include <algorithm>
 3 #include <iostream>
 4 #include <complex>
 5 #include <cstring>
 6 #include <cstdlib>
 7 #include <cstdio>
 8 #include <vector>
 9 #include <cmath>
10 #include <queue>
11 #include <stack>
12 #include <map>
13 #include <set>
14 #define rhl (31011)
15 #define inf (1<<30)
16 #define N (1010)
17 #define il inline
18 #define RG register
19 #define ll long long
20 #define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
21 
22 using namespace std;
23 
24 struct edge{ int u,v,w; }g[100010];
25 struct node{ int l,r; }a[100010];
26 
27 int fa[100010],sum[100010],n,m,k,cnt,res,ans;
28 
29 il int gi(){
30     RG int x=0,q=1; RG char ch=getchar(); while ((ch<0 || ch>9) && ch!=-) ch=getchar();
31     if (ch==-) q=-1,ch=getchar(); while (ch>=0 && ch<=9) x=x*10+ch-48,ch=getchar(); return q*x;
32 }
33 
34 il int cmp(const edge &a,const edge &b){ return a.w<b.w; }
35 
36 il int find(RG int x){ return fa[x]==x ? fa[x] : find(fa[x]); }
37 
38 il void dfs(RG int cnt,RG int x,RG int v){
39     if (x>a[cnt].r){ if (v==sum[cnt]) res++; return; }
40     dfs(cnt,x+1,v); RG int p=find(g[x].u),q=find(g[x].v);
41     if (p!=q) fa[p]=q,dfs(cnt,x+1,v+1),fa[p]=p,fa[q]=q;
42     return;
43 }
44 
45 il void work(){
46     n=gi(),m=gi();
47     for (RG int i=1;i<=m;++i) g[i].u=gi(),g[i].v=gi(),g[i].w=gi();
48     sort(g+1,g+m+1,cmp); for (RG int i=1;i<=n;++i) fa[i]=i; cnt=0;
49     for (RG int i=1;i<=m;++i){
50     if (g[i].w!=g[i-1].w) cnt++;
51     if (!a[cnt].l) a[cnt].l=i; a[cnt].r=i;
52     RG int p=find(g[i].u),q=find(g[i].v);
53     if (p!=q) fa[p]=q,k++,sum[cnt]++;
54     }
55     if (k!=n-1){ printf("0"); return; }
56     for (RG int i=1;i<=n;++i) fa[i]=i; ans=1;
57     for (RG int i=1;i<=cnt;++i){
58     res=0,dfs(i,a[i].l,0),(ans*=res)%=rhl;
59     for (RG int j=a[i].l;j<=a[i].r;++j){
60         int p=find(g[j].u),q=find(g[j].v);
61         if (p!=q) fa[p]=q;
62     }
63     }
64     printf("%d",ans); return;
65 }
66 
67 int main(){
68     File("counttree");
69     work();
70     return 0;
71 }

 

bzoj1016 [JSOI2008]最小生成树计数

标签:return   没有   之间   work   str   while   存在   ==   路径   

原文地址:http://www.cnblogs.com/wfj2048/p/6575562.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!