矩阵树定理学习笔记

前置知识:矩阵的行列式

行列式是什么?

这里写图片描述

d e t ( K ) = ( ( 1 ) τ ( P ) × D 1 , p 1 × D 2 , p 2 × D 3 , p 3 × . . . × D n , p N )

其中P为1−N的任意一个排列,τ(P)表示排列P逆序对数

形象的表示就是
这里写图片描述
在这个N=3的矩阵中,每一条线就代表着 D 1 , p 1 × D 2 , p 2 × D 3 , p 3 × . . . × D n , p N
其中可以发现,相连的斜线为/就会让逆序对数+1,\就不增加

具体怎么算呢?
比如说上图中的淡蓝色吧,ta的排列是3 1 2,逆序对应该是2
看斜线的话
一开始是/,t++,t=1,ans+=t,ans=1
然后\,t不变,t=1,ans+=t,ans=2

行列式的性质

要是向上面一样模拟的话。。。状态就有n!个,还需要一个个算的话想想就害怕
如果要快速计算行列式,我们需要知道行列式的性质

性质一:

一个矩阵行列互换(A’[i][j]=A[j][i])得到矩阵A’,det(A)=det(A’)

这个似乎显然咯

性质二:

互换行列式两行或两列的位置,det(A’)=-det(A)

这里写图片描述

那么我们有一个很好的推论:若矩阵第i行与第j行的值完全相同,det=0,因为交换这两行(列)得到的矩阵和以前一样,而det为相反数,则det=0

性质三:

矩阵A中某一行的元素全部乘以常数k,则det(A’)=k*det(A)

根据行列式的定义式,可以发现,每一行肯定会在每一个状态里出现一个属于这一行的数字,因为这一行每一个数字都扩大了k倍,可以给每一个状态带来k倍的贡献,得证

性质四:

若存在1<=i,j<=n,k为实数满足A[i][l]=k*A[j][l]对于所有1<=l<=n均成立,则det(A)=0

根据2、3性质可以得到,可以把常数k提出来,然后两行相等的话det=0,所以0*k=0

性质五:

将第i行所有元素加上任意其他行元素的实数倍,det(A)不变

证明: 考虑排列p1,p2,p3…..pn对于答案的贡献

原始矩阵:s1=sign*A[1][p1]* A[2][p2]*….*A[n][pn]

新矩阵: s2=sign*A[1][p1]* A[2][p2] A[n][pn]+sign*k * A[1][p1]* A[2][p2].. A[j][pi]*..A[n][pn]

而△=sigma(s2-s1)=0,因为△=后面加的一串,看看性质四可以发现后面的=0,所以得证

行列式怎么算?

怎么快速求行列式的值呢?这里给出结论

对于第i行,我们希望将满足i+1<=j<=n的A[j][i]的值变为0,也就是用高斯消元的方法消成一个上三角,然后把a[i][i]这一部分的值乘起来就好了。

为什么呢?因为求这个变化后的矩阵和原来的矩阵行列式一样,也就是转化为求这个矩阵的行列式,可以发现除了排列 p i = i 之外,别的排列给出的贡献都是0,所以把a[i][i]乘起来就好了

注意每次交换两行的时候要记录次数,如果是奇数,答案还要乘上-1

这样就可以O(n^3)实现矩阵行列式的求解。

矩阵树定理

求一个图的生成树个数,首先构造度数矩阵D和邻接矩阵A
这里写图片描述
基尔霍夫矩阵K=D−A
我们去掉K矩阵的第N行和第N列,然后用高斯消元(取模情况下要使用辗转相除的高斯消元)得到新矩阵的上三角形
对角线乘积的绝对值(a[i][i])就是生成树个数

这里献上裸题:SPOJ HIGH

代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#define LL long long 
using namespace std;
const double eps=1e-9;
const int N=20;
int n;double a[N][N],ans;
void gauss()
{
    int opt=0;
    for (int i=1;i<=n;i++)
    {
        if (a[i][i]<eps)
        {
            int num=i; 
            for (int j=i+1;j<=n;j++)
              if (fabs(a[j][i])>fabs(a[num][i])) num=j;
            if (num==i) {ans=0;return;}
            for (int j=i;j<=n;j++) swap(a[num][j],a[i][j]);
            opt^=1;
        }
        for (int j=i+1;j<=n;j++)
        {
            double t=a[j][i]/a[i][i];
            for (int k=i;k<=n;k++) a[j][k]-=a[i][k]*t;
        }
    }
    for (int i=1;i<=n;i++) ans*=a[i][i];
    if (opt==1) ans*=-1;  
}
int main()
{
    int T,m;scanf("%d",&T);
    while (T--)
    {
        ans=1;memset(a,0,sizeof(a));
        scanf("%d%d",&n,&m);
        for (int i=1;i<=m;i++)
        {
            int x,y;scanf("%d%d",&x,&y);
            a[x][x]++; a[y][y]++; a[x][y]--; a[y][x]--;
        }
        n--;gauss();
        printf("%.0lf\n",ans);
    }
} 

参考blog:dalaoAdalaoB

猜你喜欢

转载自blog.csdn.net/blue_cuso4/article/details/80558140