zoukankan      html  css  js  c++  java
  • HDU 4408 Minimum Spanning Tree 最小生成树计数

    Minimum Spanning Tree

    Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)

    【Problem Description】
    XXX is very interested in algorithm. After learning the Prim algorithm and Kruskal algorithm of minimum spanning tree, XXX finds that there might be multiple solutions. Given an undirected weighted graph with n (1<=n<=100) vertexes and m (0<=m<=1000) edges, he wants to know the number of minimum spanning trees in the graph.
    【Input】
    There are no more than 15 cases. The input ends by 0 0 0. For each case, the first line begins with three integers --- the above mentioned n, m, and p. The meaning of p will be explained later. Each the following m lines contains three integers u, v, w (1<=w<=10), which describes that there is an edge weighted w between vertex u and vertex v( all vertex are numbered for 1 to n) . It is guaranteed that there are no multiple edges and no loops in the graph.
    【Output】
    For each test case, output a single integer in one line representing the number of different minimum spanning trees in the graph. The answer may be quite large. You just need to calculate the remainder of the answer when divided by p (1<=p<=1000000000). p is above mentioned, appears in the first line of each test case.
    【Sample Input】
    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定理计算该点与下一组点组成的连通图中生成树的个数。最终把每一个阶段的结果相乘即可。

      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
    ");
    148         else 
    149         {
    150             ans%=mod;
    151             printf("%lld
    ",ans);
    152         }
    153     }
    154     
    155     return 0;
    156 }
    View Code
  • 相关阅读:
    【转】编写高质量代码改善C#程序的157个建议——建议147:重构多个相关属性为一个类
    【转】编写高质量代码改善C#程序的157个建议——建议146:只对外公布必要的操作
    【转】编写高质量代码改善C#程序的157个建议——建议145:避免过长的方法和过长的类
    【转】编写高质量代码改善C#程序的157个建议——建议144:一个方法只做一件事
    【转】编写高质量代码改善C#程序的157个建议——建议143:方法抽象级别应在同一层次
    【转】编写高质量代码改善C#程序的157个建议——建议142:总是提供有意义的命名
    【转】编写高质量代码改善C#程序的157个建议——建议141:不知道该不该用大括号时,就用
    【转】编写高质量代码改善C#程序的157个建议——建议140:使用默认的访问修饰符
    【转】编写高质量代码改善C#程序的157个建议——建议139:事件处理器命名采用组合方式
    SpringMVC学习总结(六)——SpringMVC文件上传例子(2)
  • 原文地址:https://www.cnblogs.com/jcf94/p/4071098.html
Copyright © 2011-2022 走看看