zoukankan      html  css  js  c++  java
  • 生成树计数

    生成树计数就是统计一张图中一共有多少种构造生成树的方案。

    大概要用到组合数学等等的数学知识。

    以下内容均来自NOI2007国家集训队论文 周冬 《生成树的计数及其应用》:

    -------------------------

    Matrix-Tree定理(Kirchhoff矩阵-树定理)。Matrix-Tree定理是解决生成树计数问题最有力的武器之一。它首先于1847年被Kirchhoff证明。在介绍定理之前,我们首先明确几个概念:

    1、G的度数矩阵D[G]是一个n*n的矩阵,并且满足:当ij时,dij=0;当i=j时,dij等于vi的度数。

    2、G的邻接矩阵A[G]也是一个n*n的矩阵, 并且满足:如果vivj之间有边直接相连,则aij=1,否则为0。

    我们定义G的Kirchhoff矩阵(也称为拉普拉斯算子)C[G]为C[G]=D[G]-A[G],则Matrix-Tree定理可以描述为:

    G的所有不同的生成树的个数等于其Kirchhoff矩阵C[G]任何一个n-1阶主子式的行列式的绝对值。

    所谓n-1阶主子式,就是对于r(1≤rn),将C[G]的第r行、第r列同时去掉后得到的新矩阵,用Cr[G]表示。

    在证明Matrix-Tree定理的过程中,我们使用了无向图G的关联矩阵BB是一个nm列的矩阵,行对应点而列对应边。B满足,如果存在一条边e={vi,vj},那在e所对应的列中,vivj所对应的那两行,一个为1、另一个为-1,其他的均为0。至于谁是1谁是-1并不重要。

    接下来,我们考察BBT。容易证明,(BBT)ij等于B的第i行与第j列的点积。所以,当i=j时,(BBT)ij等于与vi相连的边的个数,即vi的度数;而当ij时,如果有一条连接vivj的边,则(BBT)ij等于-1,否则等于0。这与Kirchhoff矩阵的定义完全相同!因此,我们得出:C=BBT!也就是说,我们可以将C的问题转化到BBT上来。

    -------------------------

    这里没办法帖公式,只好省去详细的证明过程了,详见论文原文吧:http://files.cnblogs.com/jcf94/Counting_Spanning_Tree.rar

    -------------------------

    例题有:

    SPOJ P104 Highways

    题目就是裸的生成树计数,用Matrix_Tree定理可以直接解出。

    然后贴一个Kuang神的模板:

     1 /* ***********************************************
     2 MYID    : Chen Fan
     3 LANG    : G++
     4 PROG    : Count_Spaning_Tree_From_Kuangbin
     5 ************************************************ */
     6 
     7 #include <stdio.h>
     8 #include <string.h>
     9 #include <algorithm>
    10 #include <iostream>
    11 #include <math.h>
    12 
    13 using namespace std;
    14 const double eps = 1e-8;
    15 const int MAXN = 110;
    16 
    17 int sgn(double x)
    18 {
    19     if(fabs(x) < eps)return 0;
    20     if(x < 0)return -1;
    21     else return 1;
    22 }
    23 
    24 double b[MAXN][MAXN];
    25 double det(double a[][MAXN],int n)
    26 {
    27     int i, j, k, sign = 0;
    28     double ret = 1;
    29     for(i = 0;i < n;i++)
    30     for(j = 0;j < n;j++) b[i][j] = a[i][j];
    31     for(i = 0;i < n;i++)
    32     {
    33         if(sgn(b[i][i]) == 0)
    34         {
    35             for(j = i + 1; j < n;j++)
    36             if(sgn(b[j][i]) != 0) break;
    37             if(j == n)return 0;
    38             for(k = i;k < n;k++) swap(b[i][k],b[j][k]);
    39             sign++;
    40         }
    41         ret *= b[i][i];
    42         for(k = i + 1;k < n;k++) b[i][k]/=b[i][i];
    43 
    44         for(j = i+1;j < n;j++)
    45         for(k = i+1;k < n;k++) b[j][k] -= b[j][i]*b[i][k];
    46     }
    47     if(sign & 1)ret = -ret;
    48     return ret;
    49 }
    50 
    51 double a[MAXN][MAXN];
    52 int g[MAXN][MAXN];
    53 
    54 int main()
    55 {
    56     int T;
    57     int n,m;
    58     int u,v;
    59     scanf("%d",&T);
    60     while(T--)
    61     {
    62         scanf("%d%d",&n,&m);
    63         memset(g,0,sizeof(g));
    64         while(m--)
    65         {
    66             scanf("%d%d",&u,&v);
    67             u--;v--;
    68             g[u][v] = g[v][u] = 1;
    69         }
    70         memset(a,0,sizeof(a));
    71         for(int i = 0;i < n;i++)
    72         for(int j = 0;j < n;j++)
    73         if(i != j && g[i][j])
    74         {
    75             a[i][i]++;
    76             a[i][j] = -1;
    77         }
    78         double ans = det(a,n-1);
    79         printf("%.0lf
    ",ans);
    80     }
    81     return 0;
    82 }


    最小生成树计数:

    在ACM2012金华赛区的网赛中曾经出现过这么一道题,现在是HDU 4408,统计最小生成树的个数。

    方法是Kruskal+Matrix_Tree定理,也就是把上面的算法加上Kruskal:

    来自:http://blog.csdn.net/jarily/article/details/8902402 的算法解释:

    •  *算法引入: 
    •  *给定一个含有N个结点M条边的无向图,求它最小生成树的个数t(G); 
    •  * 
    •  *算法思想: 
    •  *抛开“最小”的限制不看,如果只要求求出所有生成树的个数,是可以利用Matrix-Tree定理解决的; 
    •  *Matrix-Tree定理此定理利用图的Kirchhoff矩阵,可以在O(N3)时间内求出生成树的个数; 
    •  * 
    •  *kruskal算法: 
    •  *将图G={V,E}中的所有边按照长度由小到大进行排序,等长的边可以按照任意顺序; 
    •  *初始化图G’为{V,Ø},从前向后扫描排序后的边,如果扫描到的边e在G’中连接了两个相异的连通块,则将它插入G’中; 
    •  *最后得到的图G’就是图G的最小生成树; 
    •  * 
    •  *由于kruskal按照任意顺序对等长的边进行排序,则应该将所有长度为L0的边的处理当作一个阶段来整体看待; 
    •  *令kruskal处理完这一个阶段后得到的图为G0,如果按照不同的顺序对等长的边进行排序,得到的G0也是不同; 
    •  *虽然G0可以随排序方式的不同而不同,但它们的连通性都是一样的,都和F0的连通性相同(F0表示插入所有长度为L0的边后形成的图); 
    •  * 
    •  *在kruskal算法中的任意时刻,并不需要关注G’的具体形态,而只要关注各个点的连通性如何(一般是用并查集表示); 
    •  *所以只要在扫描进行完第一阶段后点的连通性和F0相同,且是通过最小代价到达这一状态的,接下去都能找到最小生成树; 
    •  * 
    •  *经过上面的分析,可以看出第一个阶段和后面的工作是完全独立的; 
    •  *第一阶段需要完成的任务是使G0的连通性和F0一样,且只能使用最小的代价; 
    •  *计算出这一阶段的方案数,再乘上完成后续事情的方案数,就是最终答案; 
    •  * 
    •  *由于在第一个阶段中,选出的边数是一定的,所有边的长又都为L0; 
    •  *所以无论第一个阶段如何进行代价都是一样的,那么只需要计算方案数就行了; 
    •  *此时Matrix-Tree定理就可以派上用场了,只需对F0中的每一个连通块求生成树个数再相乘即可; 

    同样,贴一个bin神模板:

      1 /* ***********************************************
      2 MYID    : Chen Fan
      3 LANG    : G++
      4 PROG    : Counting_MST_From_Kuangbin_HDU4408
      5 ************************************************ */
      6 
      7 #include <map>
      8 #include <stack>
      9 #include <queue>
     10 #include <math.h>
     11 #include <vector>
     12 #include <string>
     13 #include <stdio.h>
     14 #include <string.h>
     15 #include <stdlib.h>
     16 #include <iostream>
     17 #include <algorithm>
     18 #define N 405
     19 #define M 4005
     20 #define E
     21 #define inf 0x3f3f3f3f
     22 #define dinf 1e10
     23 #define linf (LL)1<<60
     24 #define LL long long
     25 #define clr(a,b) memset(a,b,sizeof(a))
     26 using namespace std;
     27 
     28 LL mod;
     29 struct Edge
     30 {
     31     int a,b,c;
     32     bool operator<(const Edge & t)const
     33     {
     34         return c<t.c;
     35     }
     36 }edge[M];
     37 int n,m;
     38 LL ans;
     39 int fa[N],ka[N],vis[N];
     40 LL gk[N][N],tmp[N][N];
     41 vector<int>gra[N];
     42 
     43 int findfa(int a,int b[]){return a==b[a]?a:b[a]=findfa(b[a],b);}
     44 
     45 LL det(LL a[][N],int n)
     46 {
     47     for(int i=0;i<n;i++)for(int j=0;j<n;j++)a[i][j]%=mod;
     48     long long ret=1;
     49     for(int i=1;i<n;i++)
     50     {
     51         for(int j=i+1;j<n;j++)
     52             while(a[j][i])
     53             {
     54                 LL t=a[i][i]/a[j][i];
     55                 for(int k=i;k<n;k++)
     56                     a[i][k]=(a[i][k]-a[j][k]*t)%mod;
     57                 for(int k=i;k<n;k++)
     58                     swap(a[i][k],a[j][k]);
     59                 ret=-ret;
     60             }
     61         if(a[i][i]==0)return 0;
     62         ret=ret*a[i][i]%mod;
     63         //ret%=mod;
     64     }
     65     return (ret+mod)%mod;
     66 }
     67 
     68 int main()
     69 {
     70     while(scanf("%d%d%I64d",&n,&m,&mod)==3)
     71     {
     72 
     73         if(n==0 && m==0 && mod==0)break;
     74 
     75         memset(gk,0,sizeof(gk));
     76         memset(tmp,0,sizeof(tmp));
     77         memset(fa,0,sizeof(fa));
     78         memset(ka,0,sizeof(ka));
     79         memset(tmp,0,sizeof(tmp));
     80 
     81         for(int i=0;i<N;i++)gra[i].clear();
     82         for(int i=0;i<m;i++)
     83             scanf("%d%d%d",&edge[i].a,&edge[i].b,&edge[i].c);
     84         sort(edge,edge+m);
     85         for(int i=1;i<=n;i++)fa[i]=i,vis[i]=0;
     86         int pre=-1;
     87         ans=1;
     88         for(int h=0;h<=m;h++)
     89         {
     90             if(edge[h].c!=pre||h==m)
     91             {
     92                 for(int i=1;i<=n;i++)
     93                     if(vis[i])
     94                     {
     95                         int u=findfa(i,ka);
     96                         gra[u].push_back(i);
     97                         vis[i]=0;
     98                     }
     99                 for(int i=1;i<=n;i++)
    100                     if(gra[i].size()>1)
    101                     {
    102                         for(int a=1;a<=n;a++)
    103                             for(int b=1;b<=n;b++)
    104                                 tmp[a][b]=0;
    105                         int len=gra[i].size();
    106                         for(int a=0;a<len;a++)
    107                             for(int b=a+1;b<len;b++)
    108                             {
    109                                 int la=gra[i][a],lb=gra[i][b];
    110                                 tmp[a][b]=(tmp[b][a]-=gk[la][lb]);
    111                                 tmp[a][a]+=gk[la][lb];tmp[b][b]+=gk[la][lb];
    112                             }
    113                         long long ret=(long long)det(tmp,len);
    114                         ret%=mod;
    115                         ans=(ans*ret%mod)%mod;
    116                         for(int a=0;a<len;a++)fa[gra[i][a]]=i;
    117                     }
    118                 for(int i=1;i<=n;i++)
    119                 {
    120                     ka[i]=fa[i]=findfa(i,fa);
    121                     gra[i].clear();
    122                 }
    123                 if(h==m)break;
    124                 pre=edge[h].c;
    125             }
    126             int a=edge[h].a,b=edge[h].b;
    127             int pa=findfa(a,fa),pb=findfa(b,fa);
    128             if(pa==pb)continue;
    129             vis[pa]=vis[pb]=1;
    130             ka[findfa(pa,ka)]=findfa(pb,ka);
    131             gk[pa][pb]++;gk[pb][pa]++;
    132         }
    133         int flag=0;
    134         for(int i=2;i<=n&&!flag;i++)if(ka[i]!=ka[i-1])flag=1;
    135         ans%=mod;
    136         printf("%I64d
    ",flag?0:ans);
    137     }
    138     return 0;
    139 }


    然后,最后这一题:

    HDU4305

    综合性相当强的一题:

    首先需要用计算几何的知识对平面上的点构图,形成图之后用Matrix-Tree定理求解生成树的个数。

    但是题目数据比较大,对行列式求值的时候,需要用到高斯消元求上三角阵,行列式的值等于对角线元素的积。
    由于是整数然后再mod。消元时需要求最小公倍数。还需要拓展欧几里得算法求逆元。

    日后等有人能集我们队三者之大成之后,应该就可以挑战这道题了。

  • 相关阅读:
    js中'1'到1的转换
    js类型判断
    docker安装mysql5.7
    HMM隐马尔可夫模型学习
    [python] wgs84转为gcj02坐标
    python经纬度转enu坐标
    Centos7开放及查看端口
    设计模式笔记
    npm 全面介绍
    Yarn 安装与使用详细介绍
  • 原文地址:https://www.cnblogs.com/jcf94/p/4067739.html
Copyright © 2011-2022 走看看