生成树计数问题

【题目描述】

给你一个n个点,m条边的无向图,求其生成树个数。(1<=n<=12)

【分析】

由于原问题规模较小,可以使用复杂度较高的算法,如指数级的动态规划。

那么如果n=1000呢?

【基尔霍夫矩阵】

对于无向图,它的kirchhoff矩阵定义为度数矩阵减去邻接矩阵。

在计算时,用a表示kirchhoff矩阵。则:

当i==j时,a[i][j]=i的度数。

当i!=j时,i到j有k条边相连时,a[i][j]=-k  (没有边相连为0)

例如下图

它的kirchhoff矩阵为

 

 1 for(int i=1;i<=m;i++)
 2     {
 3         int x=read(),y=read();
 4         map[x][y]=map[y][x]=1;
 5     }
 6     for(int i=1;i<=n;i++)
 7     {
 8         int d=0;
 9         for(int j=1;j<=n;j++)
10             if(map[i][j])  d++;
11         a[i][i]=d;
12     }
13     for(int i=1;i<=n;i++)
14         for(int j=1;j<=n;j++)
15             if(map[i][j])  a[i][j]=-1;

【Matrix-Tree定理】

对于一个无向图G,它的生成树个数等于其Kirchhoff矩阵任何一个n-1阶主子式的行列式的绝对值。
所谓n-1阶主子式,就是对于任意一个r,将C的第r行和第r列同时删去后的新矩阵,用Cr表示。

【行列式的计算】

首先有几点性质:

1、矩阵的某一行同时除以某个数,行列式的值不变。

2、矩阵的某两行相交换,行列式的值取相反数。

那么我们可以通过这些性质,把原矩阵化为三角形矩阵。则行列式的值就等于对角线上值得乘积。(从左上到右下的对角线)

 1 double sum=1;  
 2     for(int i=1;i<=n-1;i++)
 3     {
 4         if(a[i][i]==0)
 5         {
 6             int flag=0;
 7             for(int j=i+1;j<=n-1;j++)
 8                 if(a[j][i]!=0)
 9                 {
10                     for(int k=i;k<=n-1;k++)  swap(a[i][k],a[j][k]);
11                     flag=1;  break;
12                 }
13             if(!flag)  return 0;
14         }
15         sum*=a[i][i];
16         for(int j=i+1;j<=n-1;j++)  a[i][j]/=a[i][i];
17         for(int j=i+1;j<=n-1;j++)
18             for(int k=i+1;k<=n-1;k++)
19                 a[j][k]-=a[i][k]*a[j][i];
20     }
21     return sum;

附spoj104代码:

就是多组数据而已。。。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<cstdlib>
 5 #include<cmath>
 6 #include<ctime>
 7 #include<algorithm>
 8 using namespace std;
 9 #define MAXN 101
10 int n,m,T,map[MAXN][MAXN];  //map表示邻接矩阵,a表示基尔霍夫矩阵
11 double a[MAXN][MAXN];
12 namespace init
13 {
14     char buf[1<<15],*fs,*ft;
15     inline char getc(){return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;}
16     inline int read()
17     {
18         int x=0,f=1;  char ch=getchar();
19         while(!isdigit(ch))  {if(ch=='-')  f=-1;  ch=getchar();}
20         while(isdigit(ch))  {x=x*10+ch-'0';  ch=getchar();}
21         return x*f;
22     }
23 }using namespace init;
24 double work()
25 {
26     double sum=1;  int sign=0;
27     for(int i=1;i<=n-1;i++)
28     {
29         if(a[i][i]==0)
30         {
31             int flag=0;
32             for(int j=i+1;j<=n-1;j++)
33                 if(a[j][i]!=0)
34                 {
35                     for(int k=i;k<=n-1;k++)  swap(a[i][k],a[j][k]);
36                     flag=1;  sign++;  break;
37                 }
38             if(!flag)  return 0;
39         }
40         sum*=a[i][i];
41         for(int j=i+1;j<=n-1;j++)  a[i][j]/=a[i][i];
42         for(int j=i+1;j<=n-1;j++)
43             for(int k=i+1;k<=n-1;k++)
44                 a[j][k]-=a[i][k]*a[j][i];
45     }
46     if(sign&1)  sum=-sum;
47     return sum;
48 }
49 int main()
50 {
51     int T=read();
52     while(T--)
53     {
54         memset(a,0,sizeof(a));
55         memset(map,0,sizeof(map));
56         n=read();  m=read();
57         for(int i=1;i<=m;i++)
58         {
59             int x=read(),y=read();
60             map[x][y]=map[y][x]=1;
61         }
62         for(int i=1;i<=n;i++)
63         {
64             int d=0;
65             for(int j=1;j<=n;j++)
66                 if(map[i][j])  d++;
67             a[i][i]=d;
68         }
69         for(int i=1;i<=n;i++)
70             for(int j=1;j<=n;j++)
71                 if(map[i][j])  a[i][j]=-1;
72         double ans=work();
73         printf("%0.0lf\n",ans);
74     }
75     return 0;
76 }

 

posted @ 2016-09-13 14:25  chty  阅读(1489)  评论(0编辑  收藏  举报