( 图论专题 )【 矩阵树定理 】
首先,矩阵树定理用于求解一个图上的生成树个数。
实现方式是: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;
}