[模板] 矩阵树定理
[模板] 矩阵树定理
概念类
关联矩阵
对于 \(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;
}