【Matrix-Tree定理】初探矩阵树小结

目前我也只做过一些矩阵树的模板题,对于这个神奇的算法了解并不深入,再加上这个算法的证明需要一定的线性代数的基础,所以这篇博客目前只能说是我对于这个定理自己的理解,重点并不在于证明。

问题描述

矩阵树问题直观地说,就是给出一个图,求在这个图中生成树的方案数

问题解法

首先将这个图转换成一个矩阵,这个矩阵每一个点 (i,j)
用-1表示是否有一条边从 ij 相连,
如果 i=j ,这个位置就表示i点的度数
根据Matrix-Tree定理就可以得到:
这个图的生成树之和,就是这个矩阵删去任意一行和一列,在剩下的矩阵中的行列式的绝对值。(其实个人认为如果不是dalao看到这里就足够了,反正考场上也不会要你证明)


证明过程相当复杂,再加上我身为中年选手并没有太多时间和精力写这类证明(我看别人的都要看半天,现在一个小时不到就要肝出来明显不现实),因此证明只有主干部分,很多细节并未涉及。
首先我们定义一下几个矩阵
设C矩阵表示我们刚才所说的矩阵
设B矩阵表示一个邻接矩阵,即如有边 x 表示(u,v),那么 Bux=1,Bvx=1Bvx=1
不难发现
C=BBT,BTB
BrBr
BFrBrF
经过大约1页纸的证明我们可以得到:
如果F中存在环,那么 dat(BFr)=0
再经过大约2页纸的证明还可以得到:(E表示边集)

dat(Cr)=det(BrBTr)=FE,|F|=n1det(BFrBFTr)=FE,|F|=n1det2(BFr)

因为如果F中有环,那么值一定为0,对答案没有贡献,所以这样可以看做将每一种树都统计了。
这就是简洁得不能再简洁的证明。(我自己都觉得很坑人,所以附一下我看的证明的链接 http://blog.csdn.net/u013010295/article/details/47451451

接下来就是求行列式
通过行列式的性质:
1.交换任意两行,行列式变号。
2.把任意一行乘上一个常数加在另一行上,行列式结果不变
这样我们就可以用高斯消元,求出上三角形,这样一来整个矩阵对行列式有贡献的就只剩下主对角线上的值。将主对角线上的值乘起来就是我们要的行列式。


以下是模板题BZOJ4031的题解

题目描述:

你突然有了一个大房子,房子里面有一些房间。事实上,你的房子可以看做是一个包含n*m个格子的格状矩形,每个格子是一个房间或者是一个柱子。在一开始的时候,相邻的格子之间都有墙隔着。

你想要打通一些相邻房间的墙,使得所有房间能够互相到达。在此过程中,你不能把房子给打穿,或者打通柱子(以及柱子旁边的墙)。同时,你不希望在房子中有小偷的时候会很难抓,所以你希望任意两个房间之间都只有一条通路。现在,你希望统计一共有多少种可行的方案。

分析:

首先对于每一个房间,向周围四个是房间的点连一条边,求最后的生成树的个数即可。(其实就是板)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define SF scanf
#define PF printf
using namespace std;
void Read(int &x){
    x=0;
    char c;
    bool flag=0;
    while(c=getchar(),c!=EOF&&(c<'0'||c>'9')&&c!='-');
    if(c=='-')
        flag=1;
    else
        x=c-'0';
    while(c=getchar(),c!=EOF&&c>='0'&&c<='9')
        x=x*10+1*(c-'0');
    if(flag==1)
        x=-x;
}

#define MAXN 200
long long d[MAXN][MAXN],mod;
int w[8][4]={{0,-1},{-1,0},{1,0},{0,1}},num[MAXN][MAXN];
void swa(int x,int y,int n,int &flag){
    for(int i=1;i<=n;i++)
        swap(d[x][i],d[y][i]);
    flag^=1;
}
long long guess(int n,int m){
    int r=2,c=2,flag=0;
    for(;c<=n&&r<=m;r++,c++){
        int k=r;
        while(k<=m&&d[k][c]==0)
            k++;
        if(k>m) return 0;
        if(r!=k) swa(k,r,n,flag);
        for(int i=k+1;i<=m;i++){
            while(d[i][c]!=0){
                long long delta=d[i][c]/d[r][c];
                for(int j=c;j<=n;j++)
                    d[i][j]=(d[i][j]-delta*d[r][j])%mod;
                if(d[i][c]==0)
                    break;
                swa(i,r,n,flag);
            }
        }
    }
    if(r!=m+1)
        return 0;
    long long res=1;
    for(int i=2;i<=n;i++){
        res*=d[i][i];
        res%=mod;
    }
    if(flag==1)
        res=-res;
    return (res+mod)%mod;
}
int n,m,k;
char s[MAXN][MAXN];
int main(){
    Read(n),Read(m);
    mod=1000000000;
    int cnt=0;
    for(int i=1;i<=n;i++){
        SF("%s",s[i]);
        for(int j=0;j<m;j++)
            if(s[i][j]=='.')
                num[i][j]=++cnt;
    }
    for(int i=1;i<=n;i++)
        for(int j=0;j<m;j++)
            if(s[i][j]=='.'){
                for(int k=0;k<4;k++){
                    int x=i+w[k][0];
                    int y=j+w[k][1];
                    if(x<1||y<0||x>n||y>=m||s[x][y]=='*')
                        continue;
                    d[num[i][j]][num[x][y]]=-1;
                    d[num[i][j]][num[i][j]]++;
                }
            }
    /*for(int i=1;i<=cnt;i++){
        for(int j=1;j<=cnt;j++)
            PF("%d ",d[i][j]);
        PF("\n");
    }*/
    long long sum=guess(cnt,cnt);
    PF("%lld\n",sum);
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值