( 图论专题 )【 矩阵树定理 】

( 图论专题 )【 矩阵树定理 】

首先,矩阵树定理用于求解一个图上的生成树个数。

实现方式是:A为邻接矩阵,D为度数矩阵,则基尔霍夫(Kirchhoff)矩阵即为:K=D−A。具体实现中,记 f 为Kirchhoff矩阵,则若存在 E(u,v) ,则a[u][u]++, a[v][v]++, a[u][v]−−, a[v][u]−− 。即 f [ i ] [ i ] 为 i 点的度数,f[ i ] [ j ] 为 i, j 之间边的条数的相反数。

  这样构成的矩阵的行列式的值,就为生成树的个数。而求解行列式的快速方法为使用高斯消元进行消元消处上三角矩阵,则有对角线上的值的乘积 = 行列式的值。一般而言求解生成树个数的题目数量会非常庞大,需要取模处理。取模处理中,不能出现小数,于是使用辗转相除法:(其中因为消的是行列式,所以与消方程有所不同。交换两行行列式的值变号,且消元只能将一行的数 * k 之后加到别的行上。)

// 计算行列式的值,运用了辗转相除法,mod不是素数也能取模( 不能用费马小定理 )
int Gauss()
{
    int ans = 1;
    for ( int i=1; i<tot; i++ ) {  // 注意是到<tot, 因为最后一列不能消
        for ( int j=i+1; j<tot; j++ ) {
            while ( f[j][i] ) {
                int tmp = f[i][i]/f[j][i];
                for ( int k=i; k<=tot; k++ ) {
                    f[i][k] = ( f[i][k] - tmp*f[j][k] + mod )%mod;
                }
                swap( f[i], f[j] );
                ans = -ans;
            }
        }
        ans = ( ans*f[i][i] )%mod;
    }
    return ( ans+mod )%mod;
}

 

例题:小 Z 的房间  LibreOJ - 2122

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

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

第一行两个数分别表示 n 和 m。
接下来 n 行,每行 m 个字符,每个字符都会是 . 或者 *,其中 . 代表房间,* 代表柱子。

一行一个整数,表示合法的方案数 mod1e9。

样例输入

3 3
...
...
.*.

样例输出

15

注意:本题模数不是质数,不满足费马小定理,要用辗转相除的思想解高斯消元。

#include<bits/stdc++.h>
#define int long long

using namespace std;

const int mod = 1e9;
const int maxn = 105;
int f[maxn][maxn];
char mp[maxn][maxn];
int nxt[4][2] = {0,1,0,-1,1,0,-1,0};
int n,m,tot;

// 计算行列式的值,运用了辗转相除法,mod不是素数也能取模( 不能用费马小定理 )
int Gauss()
{
    int ans = 1;
    for ( int i=1; i<tot; i++ ) {  // 注意是到<tot, 因为最后一列不能消
        for ( int j=i+1; j<tot; j++ ) {
            while ( f[j][i] ) {
                int tmp = f[i][i]/f[j][i];
                for ( int k=i; k<=tot; k++ ) {
                    f[i][k] = ( f[i][k] - tmp*f[j][k] + mod )%mod;
                }
                swap( f[i], f[j] );
                ans = -ans;
            }
        }
        ans = ( ans*f[i][i] )%mod;
    }
    return ( ans+mod )%mod;
}

void add( int u, int v )
{
    if ( u==0 || v==0 || u<v ) return ;
    f[u][u] ++;
    f[v][v] ++;
    f[u][v] --;
    f[v][u] --;
}

signed main()
{
    cin>>n>>m;
    for ( int i=1; i<=n; i++ ) {
        for ( int j=1; j<=m; j++ ) {
            char str;cin>>str;
            if ( str=='.' ) mp[i][j]=++tot;
        }
    }
    for ( int i=1; i<=n; i++ ) {
        for ( int j=1; j<=m; j++ ) {
            for ( int k=0; k<4; k++ ) {
                int ii = i+nxt[k][0];
                int jj = j+nxt[k][1];
                if ( ii<=0||jj<=0||ii>n||jj>m ) continue ;
                add( mp[ii][jj], mp[i][j] );
            }
        }
    }
    cout << Gauss() << endl;

    return 0;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值