前置芝士
Matrix - Tree 定理,中文名:矩阵树定理。
度数矩阵
对于无向图,度数矩阵 $D$ 的定义是:
$$
D_{i,j}=
\begin{cases}
deg_i &\text{if } i=j \\
0 &\text{if } i\not =j \\
\end{cases}
$$
对于有向图,分入度矩阵 $D^{in}$ 和出度矩阵 $D^{out}$,定义如下:
$$
D^{in}_{i,j}=
\begin{cases}
in_i &\text{if } i=j \\
0 &\text{if } i\not =j \\
\end{cases}
$$
$$
D^{out}_{i,j}=
\begin{cases}
out_i &\text{if } i=j \\
0 &\text{if } i\not =j \\
\end{cases}
$$
邻接矩阵
对于无向图,邻接矩阵 $A$ 的定义为:
$$
A_{i,j}=A_{j,i}=\# e(i,j)
$$
其中 $\# e(i,j)$ 表示 $i,j$ 两点之间所连边数。
对于有向图,邻接矩阵 $A$ 的定义为:
$$
A_{i,j}=\# e(i,j)
$$
其中 $\# e(i,j)$ 表示从点 $i$ 指向 $j$ 的边数。
拉普拉斯矩阵
说到拉普拉斯就不得不提到一位恶魔系拉普拉斯妖学妹

对于无向图,拉普拉斯矩阵 $L$ 的定义为:
$$
L=D-A
$$
对于有向图,入度拉普拉斯矩阵 $L^{in}$ 和出度拉普拉斯矩阵 $L^{out}$ 的定义为:
$$
L^{in}=D^{in}-A
$$
$$
L^{out}=D^{out}-A
$$
Matrix - Tree 定理
对于无向图,其生成树的个数为($k\in [1,n]$):
$$
\text{det }L_{[n] \setminus \{k\},[n] \setminus \{k\}}
$$
其中 $\text{det }$ 表示行列式求值,$L_{[n] \setminus \{k\},[n] \setminus \{k\}}$ 表示将矩阵 $L$ 删除第 $k$ 行与第 $k$ 列后得到的矩阵。
对于有向图,其以 $k$ 为根的内向生成树(根向生成树)的个数为:
$$
\text{det }L^{out}_{[n] \setminus \{k\},[n] \setminus \{k\}}
$$
其以 $k$ 为根的外向生成树(叶向生成树)的个数为:
$$
\text{det }L^{in}_{[n] \setminus \{k\},[n] \setminus \{k\}}
$$
带权拓展
问题:对于一个带权图,定义一个生成树的权值为其所有边权之积,求其所有生成树权值之和。
这里我们修改一下度数矩阵的定义即可,不在存储边数,而是所有边边权之和,按照这种方式得到拉普拉斯矩阵后其余和上面的式子是一模一样的。
例题
P6178 【模板】Matrix-Tree 定理
带权拓展的模板。注意,这里有向图生成树的定义是以 $1$ 为根的外向树,所以我们要看入度,并删除拉普拉斯矩阵的第一行、第一列。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=305;
const int mod=1e9+7;
int gh[N][N];
int det(int n){
int res=1,f=1;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
while(gh[j][i]){
int t=gh[i][i]/gh[j][i];
for(int k=i;k<=n;k++) (((gh[i][k]-=gh[j][k]*t)%=mod)+=mod)%=mod;
swap(gh[i],gh[j]);
f=-f;
}
}
if(!gh[i][i]) return 0;
(res*=gh[i][i])%=mod;
}
return ((res*f)%mod+mod)%mod;
}
signed main(){
int n,m,t;
cin>>n>>m>>t;
for(int i=1,u,v,w;i<=m;i++){
cin>>u>>v>>w;
u--,v--;
if(t) (gh[v][v]+=w)%=mod,(((gh[u][v]-=w)%=mod)+=mod)%=mod;
else (gh[v][v]+=w)%=mod,(((gh[u][v]-=w)%=mod)+=mod)%=mod,
(gh[u][u]+=w)%=mod,(((gh[v][u]-=w)%=mod)+=mod)%=mod;
}
cout<<det(n-1);
return 0;
}P3317 [SDOI2014] 重建
推一下式子:
$$
\begin{aligned}
P&=\sum_{T \text{ 是生成树}} \left( \prod_{e \in T} p_e \prod_{e \notin T}(1 - p_e)\right) \\
&=\left( \prod_{e \in E}(1 - p_e) \right) \cdot \sum_{T \text{ 是生成树}} \left( \prod_{e \in T}\frac{p_e}{1 - p_e}\right)
\end{aligned}
$$
后面就是矩阵树定理的板子了,代码:
#include<bits/stdc++.h>
using namespace std;
#define double long double
const int N=105;
const double eps=1e-9;
double gh[N][N],res=1,f=1;
void det(int n){
for(int i=1;i<=n;i++){
int now=i;
for(int j=i+1;j<=n;j++) if(fabs(gh[j][i])>fabs(gh[now][i])) now=j;
swap(gh[i],gh[now]);
if(i!=now) f=-f;
if(gh[i][i]<eps){
res=0;
return;
}
for(int j=i+1;j<=n;j++){
if(gh[j][i]==0) continue;
double t=gh[j][i]/gh[i][i];
for(int k=i;k<=n;k++) gh[j][k]-=t*gh[i][k];
}
res*=gh[i][i];
}
}
signed main(){
int n;
cin>>n;
for(int u=1;u<=n;u++) for(int v=1;v<=n;v++){
double w;
cin>>w;
if(w>1-eps) w=1-eps;
gh[v][v]+=w/(1-w),gh[u][v]-=w/(1-w);
if(u>v) res*=(1-w);
}
det(n-1);
cout<<fixed<<setprecision(5)<<res*f;
return 0;
}P4455 [CQOI2018] 社交网络
外向生成树计数板子题。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=305;
const int mod=1e4+7;
int gh[N][N];
int det(int n){
int res=1,f=1;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
while(gh[j][i]){
int t=gh[i][i]/gh[j][i];
for(int k=i;k<=n;k++) (((gh[i][k]-=gh[j][k]*t)%=mod)+=mod)%=mod;
swap(gh[i],gh[j]);
f=-f;
}
}
if(!gh[i][i]) return 0;
(res*=gh[i][i])%=mod;
}
return ((res*f)%mod+mod)%mod;
}
signed main(){
int n,m;
cin>>n>>m;
for(int i=1,u,v,w;i<=m;i++){
cin>>v>>u;
u--,v--;
(gh[v][v]+=1)%=mod,(((gh[u][v]-=1)%=mod)+=mod)%=mod;
}
cout<<det(n-1);
return 0;
}P4336 [SHOI2016] 黑暗前的幻想乡
考虑容斥原理:
$$
所有公司都没有被漏掉的生成树方案数=\sum_{S\subseteq U} (-1)^{|S|} \times (S\ 内的公司被漏掉的生成树方案数)
$$
之后就是矩阵树定理了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=20;
const int mod=1e9+7;
vector<pair<int,int>> ve[N];
int gh[N][N],ans;
int det(int n){
int res=1,f=1;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
while(gh[j][i]){
int t=gh[i][i]/gh[j][i];
for(int k=i;k<=n;k++) (((gh[i][k]-=gh[j][k]*t)%=mod)+=mod)%=mod;
swap(gh[i],gh[j]);
f=-f;
}
}
if(!gh[i][i]) return 0;
(res*=gh[i][i])%=mod;
}
return ((res*f)%mod+mod)%mod;
}
signed main(){
int n;
cin>>n;
for(int i=1;i<n;i++){
int m;
cin>>m;
for(int j=1;j<=m;j++){
int u,v;
cin>>u>>v;
ve[i].push_back({u,v});
}
}
for(int s=0;s<(1<<(n-1));s++){
memset(gh,0,sizeof gh);
int cnt=0;
for(int i=0;i<n-1;i++){
if(!((s>>i)&1)) continue;
cnt++;
for(auto[u,v]:ve[i+1]) (gh[u][u]+=1)%=mod,(gh[v][v]+=1)%=mod,(gh[u][v]+=mod-1)%=mod,(gh[v][u]+=mod-1)%=mod;
}
ans=((ans+((n-cnt-1)&1?-1:1)*det(n-1))%mod+mod)%mod;
}
cout<<ans;
return 0;
}P2143 [JSOI2010] 巨额奖金 & P4208 [JSOI2008] 最小生成树计数
考虑所有的最小生成树的边权序列排序后都是一致的,所以我们先跑一遍最小生成树,对于这个最小生成树的每一种边权 $w$,我们在新图中先把原最小生成树中边权不为 $w$ 的边上的点进行缩点,再按照原图中所有边权为 $w$ 的边建图,跑矩阵树求生成树个数即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=105,M=1005;
const int mod=31011;
int n,m,ans=1;
int gh[N][N],nd[N];
map<int,bool> mp;
struct edge{
int u,v,w;
bool operator<(const edge b)const{
return w<b.w;
}
}e[M];
vector<edge> mst;
struct DSU{
int fa[N],siz[N];
void init(){
for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
}
int find(int x){
while(x!=fa[x]) x=fa[x]=fa[fa[x]];
return x;
}
bool merge(int x,int y){
int fx=find(x),fy=find(y);
if(fx==fy) return 0;
if(siz[fx]>siz[fy]) swap(fx,fy);
return fa[fy]=fx,1;
}
}dsu;
int det(int n){
int res=1,f=1;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
while(gh[j][i]){
int t=gh[i][i]/gh[j][i];
for(int k=i;k<=n;k++) (((gh[i][k]-=gh[j][k]*t)%=mod)+=mod)%=mod;
swap(gh[i],gh[j]);
f=-f;
}
}
if(!gh[i][i]) return 0;
(res*=gh[i][i])%=mod;
}
return ((res*f)%mod+mod)%mod;
}
bool kk(){
int res=0;
dsu.init();
sort(e+1,e+1+m);
for(int i=1;i<=m;i++){
if(!dsu.merge(e[i].u,e[i].v)) continue;
mp[e[i].w]=1,res++;
mst.push_back(e[i]);
if(res==n-1) return 1;
}
return 0;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++) cin>>e[i].u>>e[i].v>>e[i].w;
if(!kk()){
cout<<0;
return 0;
}
for(auto[val,tag]:mp){
int idx=0;
dsu.init();
memset(gh,0,sizeof gh);
for(auto[u,v,w]:mst){
if(w==val) continue;
dsu.merge(u,v);
}
for(int i=1;i<=n;i++) if(dsu.find(i)==i) nd[i]=++idx;
for(int i=1;i<=n;i++) if(dsu.find(i)!=i) nd[i]=nd[dsu.find(i)];
for(int i=1;i<=m;i++){
if(e[i].w!=val) continue;
int x=nd[e[i].u],y=nd[e[i].v];
(gh[x][x]+=1)%=mod,(gh[y][y]+=1)%=mod,
(gh[x][y]+=mod-1)%=mod,(gh[y][x]+=mod-1)%=mod;
}
(((ans*=det(idx-1))%=mod)+=mod)%=mod;
}
cout<<ans;
return 0;
}


Comments NOTHING