【暖*墟】#图论计数# 矩阵树定理的学习与练习
【基本概念及实现】
矩阵树定理用于求解图上生成树的个数。
实现方式是:A为邻接矩阵,D为度数矩阵,则基尔霍夫(Kirchhoff)矩阵即为:K=D−A。
得到K:记a为Kirchhoff矩阵,若存在E(u,v),则a[u][u]++,a[v][v]++,a[u][v]−−,a[v][u]−−。
那么,a[i][i]为i点的度数,a[i][j]为 i,j之间边的条数的相反数,就构造出K矩阵了。
- 这样构成的矩阵K的行列式的值,就为此图中生成树的个数。
求解行列式的快速方法:使用高斯消元进行消元消出上三角矩阵,则有'对角线上的值的乘积=行列式的值'。
一般而言求解生成树个数的题目数量会非常庞大,需要取模处理。
有些情况下模数不为质数,因此不能直接在模意义下消元,需要使用辗转相除法。
int Gauss(){
int ans=1; for(int i=1;i<=tot;i++){
for(int j=i+1;j<=tot;j++) //tot是总点数
while(f[j][i]){ int t=f[i][i]/f[j][i];
for(int k=i;k<=tot;k++)
f[i][k]=(f[i][k]-t*f[j][k]%mod+mod)%mod,
swap(f[i][k],f[j][k]); ans=-ans; //辗转相除法
} ans=(ans*f[i][i])%mod;
} return (ans+mod)%mod; //注意ans可能为负数
}
变元矩阵树定理:求所有生成树的总边积的和。
- 和矩阵树的求法类似,a[i][i]记录总的边权和,a[i][j]记录i,j之间边权的相反数。
【例题1】P4111 小Z的房间
- 你突然有了一个大房子,房子里面有一些房间。房子可以看做是一个包含n×m个格子的矩形,
- 每个格子是一个房间或者是一个柱子。在一开始的时候,相邻的格子之间都有墙隔着。
- 你想要打通一些相邻房间的墙,使得所有房间能够互相到达。
- 在此过程中,你不能把房子给打穿,或者打通柱子(以及柱子旁边的墙)。
- 同时,你不希望在房子中有小偷的时候会很难抓,所以你希望任意两个房间之间都只有一条通路。
- 现在,你希望统计一共有多少种可行的方案。答案对10^9 取模。1<=n,m<=9。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<cmath>
using namespace std;
typedef long long ll;
//【p4111】小Z的房间 // 求图中生成树(所有节点全部连通的树)的数量
//【矩阵树定理】Kirchhoff矩阵的行列式的值,就是图中生成树的个数。
// 注意:不能加入柱子的点。加边时,只枚举(i,j)上方和左方的点,可以避免重复。
void reads(int &x){ //读入优化(正负整数)
int fx_=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')fx_=-1;s=getchar();}
while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
x*=fx_; //正负号
}
const int mod=1e9; int n,m,f[109][109],id[19][19],tot=0;
void add(int u,int v){ f[u][v]--,f[v][u]--,f[u][u]++,f[v][v]++; }
int Gauss(){
int ans=1; for(int i=1;i<tot;i++){
for(int j=i+1;j<tot;j++) //tot是总点数
while(f[j][i]){ int t=f[i][i]/f[j][i];
for(int k=i;k<tot;k++)
f[i][k]=(f[i][k]-1LL*t*f[j][k]%mod+mod)%mod;
std::swap(f[i],f[j]),ans=-ans; //辗转相除法
} ans=(1LL*ans*f[i][i])%mod;
} return (ans+mod)%mod; //注意ans可能为负数
}
char ss[19][19];
int main(){
reads(n),reads(m); for(int i=1;i<=n;i++) scanf("%s",ss[i]+1);
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
if(ss[i][j]=='.') id[i][j]=++tot; //给每个非墙的点(构成树的点)编号
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(ss[i][j]=='.'){
if(id[i-1][j]) add(id[i][j],id[i-1][j]); //向上方连边
if(id[i][j-1]) add(id[i][j],id[i][j-1]); //向左边连边
} printf("%d\n",Gauss()); //矩阵树定理求图中生成树的数量
}
细节问题就是:
for(int k=i;k<tot;k++)
f[i][k]=(f[i][k]-1LL*t*f[j][k]%mod+mod)%mod;
std::swap(f[i],f[j]),ans=-ans; //辗转相除法
在本地编译不过,所以也可以写成:
for(int k=i;k<tot;k++)
f[i][k]=(f[i][k]-1LL*t*f[j][k]%mod+mod)%mod,
swap(f[i][k],f[j][k]); ans=-ans; //辗转相除法
【例题2】P4336 黑暗前的幻想乡
- n个点、n−1种颜色,某种颜色可以涂某些边。
- 求图中n-1条边都不同色的生成树的数量 % 10^9+7 。
【容斥原理】合法方案数=总生成树方案数-缺一种颜色的方案数+缺两种颜色的方案数……
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<cmath>
using namespace std;
typedef long long ll;
//【p4336】黑暗前的幻想乡 // 矩阵树定理 + 容斥原理
//n个点、n−1种颜色,某种颜色可以涂某些边。
//求图中n-1条边都不同色的生成树的数量 % 10^9+7 。
//【矩阵树定理】Kirchhoff矩阵的行列式的值,就是图中生成树的个数。
//【容斥原理】合法方案数=总生成树方案数-缺一种颜色的方案数+缺两种颜色的方案数……
void reads(int &x){ //读入优化(正负整数)
int fx_=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')fx_=-1;s=getchar();}
while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
x*=fx_; //正负号
}
const int mod=1e9+7; int n,m,f[19][19],anss=0;
void add(int u,int v){ f[u][v]--,f[v][u]--,f[u][u]++,f[v][v]++; }
int Gauss(){
int ans=1; for(int i=1;i<n;i++){
for(int j=i+1;j<n;j++) //n是总点数
while(f[j][i]){ int t=f[i][i]/f[j][i];
for(int k=i;k<n;k++)
f[i][k]=(f[i][k]-1LL*t*f[j][k]%mod+mod)%mod,
swap(f[i][k],f[j][k]); ans=-ans; //辗转相除法
} ans=(1LL*ans*f[i][i])%mod;
} return (ans+mod)%mod; //注意ans可能为负数
}
vector < pair<int,int> > q[19];
int main(){
reads(n); for(int i=1;i<n;i++)
{ reads(m); for(int j=1,x,y;j<=m;j++)
reads(x),reads(y),q[i].push_back(make_pair(x,y)); }
for(int i=0;i<(1<<(n-1));i++){ //枚举某种修建状态【穷举+容斥】
int cnt=0; memset(f,0,sizeof(f));
for(int j=1;j<n;j++) if(i&(1<<(j-1))){
for(int k=0;k<q[j].size();k++) //如果状态i中修了边j
add(q[j][k].first,q[j][k].second); //将边放入矩阵K
cnt++; //此状态i下,修建的边数
} if((n-cnt)&1) anss=(anss+Gauss())%mod;
else anss=(anss-Gauss()+mod)%mod;
} cout<<anss<<endl; //最终得到的答案:
}
——时间划过风的轨迹,那个少年,还在等你。