• 矩阵树定理


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

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

    问题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知:交换两行后,行列式的值$ imes -1$,那么在实数范围内仅有$0=-0$,得证。

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

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


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

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

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

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

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

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

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

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

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

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

     

    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
    ",ans%p);
    }
  • 相关阅读:
    python学习笔记
    【JavaScript】如何判断一个对象是未定义的?(已解决)
    【Eclipse】一个简单的 RCP 应用 —— 显示Eclipse 的启动时间。
    Win7 系统如何关闭休眠功能?(已解决)
    【Eclipse】Ubuntu 下菜单栏失效了,怎么办?(已解决)
    【Ubuntu】更新系统时出现Hash校验和不符的错误(已解决)
    【wget】一条命令轻松备份博客(包括图片)
    【Eclipse】启动时报错:No Java virtual machine (已解决)
    git 命令自动补全
    快马和慢马
  • 原文地址:https://www.cnblogs.com/kamimxr/p/13121575.html
Copyright © 2020-2023  润新知