Minimum Spanning Tree
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
5 10 12 2 5 3 2 4 2 3 1 3 3 4 2 1 2 3 5 4 3 5 1 3 4 1 1 5 3 3 3 2 3 0 0 0
【Sample Output】
4
【题意】
一张无向图,要求求出其中最小生成树的棵树。
【分析】
生成树计数可以使用Matrix-Tree定理解决,本题最主要的区别是有了一个最小生成树的额外条件。
首先考虑一下如何得到最小生成树。
Kruskal算法的基本思想是,按照边长排序,然后不断将短边加入集合,最终一步如果能成功把n-1条边都加入同一个集合,则找到了最小生成树。在维护集合时,可以使用并查集来快速处理。
如果把Kruskal的过程按照边长划分成多个阶段,实际上是处理了所有短边的连通性之后继续处理下一个长度的边的连通性,并依次继续处理剩下边的连通性。然后我们可以发现,不同长度的边之间的连通性互不影响!!!
假设存在n1条长度为c1的边,n2条长度为c2的边...则Kruskal首先处理c1边的连通性,然后处理c2边的连通性,对于c1边的连通性的处理可能有多种方案,即从n1条边中取出一定数量的边构成最大连通图,但是最终处理完之后的结果对于c2来说是完全一样的。因此算法就出来了,在Kruskal的基础上,使用Matrix-Tree定理处理每个阶段生成树的种数,最后将所有阶段的结果相乘即可。
具体实现为:
在Kruskal的基础上,每完成一个阶段(检查完一个长度),就将所有遍历过的点缩成一个点,然后用Matrix-Tree定理计算该点与下一组点组成的连通图中生成树的个数。最终把每一个阶段的结果相乘即可。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 /* *********************************************** 2 MYID : Chen Fan 3 LANG : G++ 4 PROG : Counting_MST_HDU4408 5 ************************************************ */ 6 7 #include <vector> 8 #include <cstdio> 9 #include <cstring> 10 #include <iostream> 11 #include <algorithm> 12 #include <bitset> 13 #define N 405 14 #define M 4005 15 16 using namespace std; 17 18 typedef struct nod 19 { 20 int a,b,c; 21 } node; 22 node edge[M]; 23 24 bool op(node a,node b) 25 { 26 return a.c<b.c; 27 } 28 29 int n,m,o,fa[N],ka[N]; 30 long long ans,mod,gk[N][N],kir[N][N]; 31 32 bitset<N> flag; 33 vector<int> gra[N]; 34 35 int getfather(int x,int father[]) 36 { 37 if (father[x]!=x) father[x]=getfather(father[x],father); 38 return father[x]; 39 } 40 41 long long det(long long a[][N],int n) //Matrix-Tree定理求Kirchhoff矩阵 42 { 43 for (int i=0;i<n;i++) 44 for (int j=0;j<n;j++) a[i][j]%=mod; 45 long long ret=1; 46 for (int i=1;i<n;i++) 47 { 48 for (int j=i+1;j<n;j++) 49 while (a[j][i]) 50 { 51 long long t=a[i][i]/a[j][i]; 52 for (int k=i;k<n;k++) a[i][k]=(a[i][k]-a[j][k]*t)%mod; 53 for (int k=i;k<n;k++) swap(a[i][k],a[j][k]); 54 ret=-ret; 55 } 56 if (a[i][i]==0) return 0; 57 ret=ret*a[i][i]%mod; 58 //ret%=mod; 59 } 60 return (ret+mod)%mod; 61 } 62 63 void matrix_tree() 64 { 65 for (int i=1;i<=n;i++) //根据访问标记找出连通分量 66 if (flag[i]) 67 { 68 gra[getfather(i,ka)].push_back(i); 69 flag[i]=0; 70 } 71 for (int i=1;i<=n;i++) 72 if (gra[i].size()>1) //枚举连通分量 73 { 74 memset(kir,0,sizeof(kir)); 75 76 int len=gra[i].size(); 77 for (int a=0;a<len;a++) 78 for (int b=a+1;b<len;b++) 79 { 80 int la=gra[i][a],lb=gra[i][b]; 81 kir[b][a]-=gk[la][lb]; 82 kir[a][b]=kir[b][a]; 83 kir[a][a]+=gk[la][lb]; 84 kir[b][b]+=gk[la][lb]; 85 } //构造矩阵 86 87 long long ret=det(kir,len); 88 ret%=mod; 89 ans=(ans*ret%mod)%mod; 90 91 for (int a=0;a<len;a++) fa[gra[i][a]]=i; 92 } 93 for (int i=1;i<=n;i++) //连通图缩点+初始化 94 { 95 fa[i]=getfather(i,fa); 96 ka[i]=fa[i]; 97 gra[i].clear(); 98 } 99 } 100 101 int main() 102 { 103 freopen("4408.txt","r",stdin); 104 105 while (scanf("%d%d%lld",&n,&m,&mod)==3) 106 { 107 108 if (n==0&&m==0&&mod==0) break; 109 for (int i=1;i<=m;i++) scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].c); 110 sort(&edge[1],&edge[m+1],op); 111 112 for (int i=1;i<=n;i++) gra[i].clear(); 113 for (int i=1;i<=n;i++) 114 { 115 fa[i]=i; 116 ka[i]=i; 117 } 118 flag.reset(); 119 memset(gk,0,sizeof(gk)); 120 ans=1; 121 o=edge[1].c; 122 for (int i=1;i<=m;i++) 123 { 124 int pa=getfather(edge[i].a,fa),pb=getfather(edge[i].b,fa); 125 if (pa!=pb) 126 { 127 flag[pa]=1; 128 flag[pb]=1; //访问标记 129 ka[getfather(pa,ka)]=getfather(pb,ka); 130 gk[pa][pb]++; 131 gk[pb][pa]++; //邻接矩阵 132 } 133 if (i==m||edge[i+1].c!=o) //所有相同的边并成一组 134 { 135 matrix_tree(); 136 o=edge[i+1].c; 137 } 138 } 139 140 bool done=true; 141 for (int i=2;i<=n;i++) 142 if(ka[i]!=ka[i-1]) 143 { 144 done=false; 145 break; 146 } 147 if (!done) printf("0\n"); 148 else 149 { 150 ans%=mod; 151 printf("%lld\n",ans); 152 } 153 } 154 155 return 0; 156 }