矩阵树定理

矩阵树就是指计算一个图生成树的个数(无论有没有环,边带不带权,有向或无向,指不指定根节点,外向树或内向树,都可以计算)

在此之前,我们引入一个问题:

问题1:给定一个n个节点的完全图,求其生成树个数。

这个问题,简单的prufer序列就可以解决,显然,答案是$n^{n-2}$

一次升级版: 

问题2:给定一个n个节点的无边权无向图,求其生成树个数。 

怎么解决?还是prufer序列?似乎不太可行。

这时候,计数利器-------线性代数出现了

 

什么是线性代数?没学过?不着急,矩阵乘法总学过吧。那就没问题。

我们对于一个矩阵,定义一个值,叫做行列式的值。

这个值的求法如下:

我们有着$n!$个1~n的排列p,

对于一个n阶矩阵:$\sum_{1}^{n!} (-1)^{f(p)}\prod_{j=1}^{n}a_{j,p_j}$这个便是这个n阶矩阵的行列式的值

其中:$f(p)$表示当排列是p的情况下逆序对的个数。

我们要求这个值怎么办?暴力显然不可取。

当暴力不行的时候,我们挖掘值的性质。

性质1

交换矩阵的任意两行,行列式变号。

证明:一个排列交换其中任意两个元素,逆序对个数变化量为奇数。(不会的去学学逆序对)。

 

性质2

矩阵的一行都乘以 k ,行列式也乘以 k

证明:根据行列式值得定义,每一行仅仅会取一个元素,所以每一个累加中答案乘k,总答案也就乘k。

 

性质3

如果矩阵内有两行相等,那么行列式为 0

证明:由性质1知:交换两行后,行列式的值$\times -1$,那么在实数范围内仅有$0=-0$,得证。

 

性质4
如果一行是另一行的 k 倍,那么行列式值为0。

证明:由性质2和3易证,如果一行乘k,那么会存在两行相等。即:答案乘k后等于0。在实数范围内,仅有0可能是答案,得证。

 


性质5
一行加上另一行的 k 倍,行列式不变。

证明:我们可以把现矩阵的行列式拆成两个行列式相加,一个是原先矩阵的行列式,另一个是增长的行列式。根据性质 4 ,可以得到增长的行列式为 0 。

 

看到这里,相比很多人已经能明白了吧,如果我们可以得到一个上三角矩阵,那么正对角线的乘积便是其行列式的值。

那么如何得到这个上三角矩阵呢?显然是通过高斯消元在$n^3$的时间内得到上三角矩阵。

但要注意,和普通的高斯消元不同的是:我们交换某两行,行列式的值$\times -1$。

其余的便没什么大问题了。

 

但到目前为止对于求生成树个数应该还没什么头绪那吧,那么,我们引来一尊大人物:Kirchhoff矩阵

Kirchhoff 矩阵是指的对于一个图构造出来的一个矩阵。具体定义为度数矩阵减去邻接矩阵。

度数矩阵:$\left\{\begin{matrix}degree &,i==j \\ 0 & ,otherwise \end{matrix}\right.$

至于邻接矩阵便是我们存图时经常用的东西。

 

 

Matrix Tree定理
一个图中的生成树个数等于其Kirchhoff 矩阵的任意一个 代数余子式的行列式(即:随意去掉矩阵中的第k行和第k列,注意一定要是同一行和列,然后剩下的矩阵跑高斯消元就好了)。
由于其证明繁杂,这里就不说了,感兴趣的可以了解其他大神的博客。

 

到目前为止,我们可以完成圆满的完成问题2了。可是在NOI的范围内,这还远远不够,让我们接着看:

问题3:给定一个n个节点的带边权无向图,定义其一个生成树T的权值为T中所有边权的乘积。求其所有生成树的权值之和。
我们定义重边:对于两个点(x,y)之间若有大于1条边直接相连,那么这几条边就都可以叫做重边。

 

容易理解 : 带重边的情况,上面的经典矩阵树定理也是能够处理的。

根据乘法原理,对于某种生成树的形态,其贡献为每条边重复的次数的乘积。

如果把重复边次数理解成权值的话,那么矩阵树定理求的就是 : 所有生成树边权乘积的总和。

因此我们将度数矩阵变成与相邻边的权值和,邻接矩阵从0,1变成边的权值,然后跑矩阵树即可。

 

问题3.5:给定一个n个节点的无边权无向图,以k节点为根,求以k节点为根的生成树个数

这个问题其实并不存在,因为无向图的生成树必定是无根树。无论是否指定根节点对答案都无影响。但我将其放在这里的原因是因为与下面的有根树有所对应对应:我们指定了根节点。

我们发现,若以k为根,那么在求基尔霍夫矩阵的代数余子式的行列式值的时候,限定删去第k行和第k列,这样就可以

 

问题4:给定一个n个节点的带边权有向图,以1为根,求其外向树的个数。
外向树,顾名思义,就是所有边都从1开始走,一直走到叶子节点。
我们把度数矩阵改为:到该点的边权总和。接着求基尔霍夫矩阵的代数余子式的行列式值的时候,限定删去第1行和第1列然后跑矩阵树即可。

 

问题5:给定一个n个节点的带边权有向图,以1为根,求其内向树的个数。
外向树,顾名思义,就是所有边都从叶子节点开始走,一直走到1号点。
我们把度数矩阵改为:从该点出发的边权总和。接着求基尔霍夫矩阵的代数余子式的行列式值的时候,限定删去第1行和第1列然后跑矩阵树即可。

 

接下来便是愉悦的代码时间:

 

#include <bits/stdc++.h>
#define inc(i,a,b) for(register int i=a;i<=b;i++)
using namespace std;
const int p=1e9+7;
int mmap[321][321];
long long kir[321][321];
int n,m,t,line,id[321];
long long KSM(long long a,long long b){
    long long res=1;
    while(b){
        if(b&1) res=res*a%p;
        a=a*a%p;
        b/=2;
    }
    return res;
}
long long ans=1,cnt=0;
void GUASS(){
    inc(i,2,n){
        int maxn=i;
        inc(j,i +1,n) if(abs(kir[j][i])>abs(kir[maxn][i])) maxn=j;
        if(!kir[maxn][i]){
            ans=0;
            return;
        }
        inc(j,i,n) swap(kir[maxn][j],kir[i][j]);
        if(maxn!=i) ++cnt;
        inc(j,2,n){
            if(j==i) continue;
            long long tmp=kir[j][i]*KSM(kir[i][i],p-2)%p;
            inc(k,i,n) kir[j][k]=(kir[j][k]-kir[i][k]*tmp%p+p)%p;
        }
    }
}
int main(){
    //freopen("1.in","r",stdin);
    //freopen("")
    cin>>n>>m>>t;
    inc(i,1,m){
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        if(t==1){
            kir[x][y]=(kir[x][y]-z+p)%p;
            kir[y][y]=(kir[y][y]+z)%p;
        }
        else{
            kir[x][y]=(kir[x][y]-z+p)%p;
            kir[y][y]=(kir[y][y]+z)%p;            
            kir[y][x]=(kir[y][x]-z+p)%p;
            kir[x][x]=(kir[x][x]+z)%p;
        }
    }
    GUASS();
    inc(i,2,n) ans=ans*kir[i][i]%p;
    if(cnt&1) ans=p-ans;
    printf("%lld\n",ans%p);
}

 

posted @ 2020-06-13 20:00  神之右大臣  阅读(809)  评论(0编辑  收藏  举报