[模板] 矩阵树定理

[模板] 矩阵树定理

[HEOI2015]小 Z 的房间

概念类

关联矩阵

对于 \(n\) 个点 \(m\) 条边的无向图,定义关联矩阵 \(G:\)

对于途中第 \(k\) 条无向边 \((u,v)\),令 \(G_{(u,k)}=-1,G_{(v,k)=1}\)

对于这个无向图来说,关联矩阵长这样:

可以清楚的发现:

  • 关联矩阵的每一行表示一个点的所有信息。
  • 关联矩阵的每一列表示一条边的所有信息。

基尔霍夫矩阵

定义基尔霍夫矩阵 \(K=GG^T\),也就是关联矩阵 \(\times\) 关联矩阵的转置。

拆开不难发现:

\(GG^T_{(i,j)}=\sum_{k=1}^n G_{(i,k)}G^T_{(k,i)}=\sum_{k=1}^nG_{(i,k)}G_{(j,k)}\)

矩阵大小为 \(n\times n\)

也就是关联矩阵两行的点积

考虑两条性质:

  • 如果 \(i=j\) ,那么 \(K_{(i,i)}\) 表示一个点的点度。

  • 如果 \(i\not=j\),那么 \(K_{(i,j)}=-1\) 则表示 \((i,j)\) 之间存在边(没有重边),如果 \(=0\) 则表示 \((i,j)\) 之间不存在边。

根据这两个性质,可以构造出 \(K\)

inline void link(int u,int v){
	G[u][u]++;G[v][v]++;
	G[u][v]--;G[v][u]--;//如果没有重边可以改为G[u][v]=G[v][u]=-1
}

定义度数矩阵 \(D:\)

\(D_{(i,i)}=deg_i\),其他位置为 \(0\) 。(只有对角线上有数,表示数的点度)

可以得到:

\(K=D-B\)


解法

首先构造出原图的基尔霍夫矩阵,去掉一行一列。

辗转相除消元

在模数不是质数的时候,可以利用辗转相除法消元,使得基尔霍夫矩阵得到上三角结构。

具体来说对于一个方阵 \(T_n\)

枚举 \([1,n-1]\) 需要消掉的变元,利用第 \(i\) 行和第 \(j\) 行来消掉所有的第 \(i\) 列的数。

手推一下即可发现辗转相除法的原理。

for(int i=1;i<cnt;i++){//枚举消掉的变元
		for(int j=i+1;j<=cnt;j++){
			while(G[j][i]){//当a[j][i]==0时完成消掉一列的一步
				int t=G[i][i]/G[j][i];
				for(int k=1;k<=cnt;k++)G[i][k]=(G[i][k]-G[j][k]*t%P+P)%P;
				swap(G[i],G[j]);ans=-ans;
			}
		}
	}

记得换行后需要取负

最后得到三角矩阵的主对角线的乘积就是结果。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
	T x=0;char ch=getchar();bool fl=false;
	while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
	}
	return fl?-x:x;
}
#define int long long
const int P = 1e9;
const int maxn = 20;
int G[maxn<<2][maxn<<2],id[maxn][maxn],cnt=0;
inline void link(int u,int v){
	G[u][u]++;G[v][v]++;G[u][v]=G[v][u]=-1;
}
int n,m;
char ch[maxn][maxn];
#define read() read<long long>()
signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++){
		cin>>(ch[i]+1);
		for(int j=1;j<=m;j++)if(ch[i][j]=='.')id[i][j]=++cnt;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			if(ch[i][j]=='.' && ch[i+1][j]=='.')link(id[i][j],id[i+1][j]);
			if(ch[i][j]=='.' && ch[i][j+1]=='.')link(id[i][j],id[i][j+1]);
		}
	//cerr<<"@"<<endl;//
	cnt--;
	int ans=1LL;
	//xiao yuan ---> 三角矩阵
	for(int i=1;i<cnt;i++){
		for(int j=i+1;j<=cnt;j++){
			while(G[j][i]){//当a[j][i]==0时完成消掉一列的一个元素
				int t=G[i][i]/G[j][i];
				for(int k=1;k<=cnt;k++)G[i][k]=(G[i][k]-G[j][k]*t%P+P)%P;
				swap(G[i],G[j]);ans=-ans;
			}
		}
	}
	for(int i=1;i<=cnt;i++)ans=(ans*G[i][i]%P+P)%P;
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-08-12 17:34  ¶凉笙  阅读(72)  评论(0编辑  收藏  举报