矩阵树定理

矩阵树定理

用于解决一张图的生成树计数问题。(默认图可以存在重边,但不能存在自环)

前置芝士:行列式

\(N \times N\) 的矩阵的行列式,可以理解为 \(n\) 个列向量所夹几何体的有向体积。
例如,2 阶行列式即为两个列向量的叉积。

\[\left[ \begin{array}{l} 1 & 2\\ 2 & 1 \end{array} \right]=-3 \]

行列式有公式

\[\operatorname{det}(A)=\sum_{\sigma \in S_{n}} \operatorname{sgn}(\sigma) \prod_{i=1}^{n} a_{i, \sigma(i)} \]

其中 \(S_n\) 是指长度为 \(n\) 的全排列的集合,\(\sigma\) 就是一个全排列,如果 \(\sigma\) 的逆序对对数为偶数,则 \(\operatorname{sgn}(\sigma)=1\),否则\(\operatorname{sgn}(\sigma)=-1\)
行列式有如下性质:

  1. 两行交换,行列式变为相反数。
  2. 一行 加上 另一行乘某个常数的积,行列式不变。
  3. 三角矩阵的行列式等于对角线之积。
    因为行列式符合上述性质,所以可以直接通过高斯消元计算。

当需要模上一个数时,若模数为质数,算乘积时则可直接使用逆元,否则可以使用辗转相除法。

在线计算行列式工具

无向图

设度数矩阵 \(D\)\(D_{i,i}=\operatorname{deg}(i),\ D_{i,j}=0, i \neq j\)
设邻接矩阵 \(A\)\(A_{i,j}=1\)\(i, j\) 之间有连边。
设拉普拉斯矩阵 \(L=D-A\),所以,

  1. \(L_{i,i}\)\(i\) 点度数。
  2. \(L_{i,j}\)\(i, j\) 间的边数的相反数。

\(G\) 的生成树个数即为拉普拉斯矩阵去掉任意第 \(k\) 行,第 \(k\) 列(行、列序号相等)后的行列式。

有向图

有向图分为内向树和外向树。
设入度矩阵 \(D^{in}\)\(D_{i,i}=\operatorname{deg^{in}}(i),\ D_{i,j}=0, i \neq j\)
类似定义出度矩阵\(D^{out}\)
设邻接矩阵 \(A\)\(A_{i,j}=1\)\(i\)\(j\) 之间有连边。
设拉普拉斯矩阵 \(L^{in}=D^{in}-A\)
类似定义 \(L^{out}\)
\(t^{root}{G, r}\) 表示图 \(G\) 中,以 \(r\) 为根,所有边指向父亲的生成树。
\(t^{leaf}{G, r}\) 表示图 \(G\) 中,以 \(r\) 为根,所有边指向儿子的生成树。
\(t^{root}{G,r}\) 的值为 \(L^{out}\) 去掉第 \(r\) 行,第 \(r\) 列后的行列式。因为根更好向外生长,所以根向生成树对应出度矩阵(雾
\(t^{leaf}{G,r}\) 的值为 \(L^{in}\) 去掉第 \(r\) 行,第 \(r\) 列后的行列式。

拓展

当原图有边权时:

  1. \(L_{i,i}\) 的值为与 \(i\) 点有关的边的权值和。如,入度矩阵就统计入边的权值和。
  2. \(L_{i,j}\) 的值为 \(i\)\(j\) 的边权和。
    此时求出来的值为

\[\sum_{T \in S} \prod_{e_i \in T}e_i \]

\(S\) 为图 \(G\) 的生成树集合。

P4111 [HEOI2015]小 Z 的房间

模板题,求无向图的生成树个数,需要用辗转相除法。

#include<cstdio>
#include<iostream>
using namespace std;

const int MOD = 1e9;

int n, m, cnt, mp[15][15];
int a[105][105];
char s[15];

int main() {
	scanf("%d%d",&n, &m);
	for(int i = 1; i <= n; i++) {
		scanf("%s",s + 1);
		for(int j = 1; j <= m; j++)
		 if(s[j] == '.') mp[i][j] = ++cnt;
	}
	for(int i = 1; i <= n; i++)
	 for(int j = 1; j <= m; j++)
	  if(mp[i][j]) {
	  	if(mp[i - 1][j]) ++a[mp[i][j]][mp[i][j]], --a[mp[i][j]][mp[i - 1][j]];
	  	if(mp[i + 1][j]) ++a[mp[i][j]][mp[i][j]], --a[mp[i][j]][mp[i + 1][j]];
	  	if(mp[i][j - 1]) ++a[mp[i][j]][mp[i][j]], --a[mp[i][j]][mp[i][j - 1]];
	  	if(mp[i][j + 1]) ++a[mp[i][j]][mp[i][j]], --a[mp[i][j]][mp[i][j + 1]];
	  }
	--cnt; //去除最后一行和列 
	int res = 1;
	for(int i = 1; i <= cnt; i++)
	 for(int j = i + 1; j <= cnt; j++)
	  while(a[j][i]) {
	  	//辗转相除
	  	int r = a[i][i] / a[j][i];
	  	for(int k = i; k <= cnt; k++) a[i][k] = (a[i][k] - 1ll * r * a[j][k] % MOD + MOD) % MOD;
	  	swap(a[i], a[j]); res *= -1;
	  }
	  for(int i = 1; i <= cnt; i++) res = 1ll * res * a[i][i] % MOD;
	  printf("%d\n",(res + MOD) % MOD);
	return 0;
}

P6178 【模板】Matrix-Tree 定理

模板题,带边权,求无向图与以 1 为根的外向树。

#include<cstdio>
#include<iostream>
#define LL long long
using namespace std;

inline int read() {
	int res = 0, f = 1; char ch;
	while((ch = getchar()) && (ch < '0' || ch > '9') && ch != '-');
	(ch == '-') ? f = -1 : res = ch - '0';
	while((ch = getchar()) && ch >= '0' && ch <= '9') res = (res << 3) + (res << 1) + ch - '0';
	return res * f;
}

const int MAXN = 305;
const LL MOD = 1e9 + 7;

int n, m, t;
LL a[MAXN][MAXN];

inline LL expow(LL x, LL y) {
	LL res = 1;
	while(y) {
		if(y & 1) res = res * x % MOD;
		y >>= 1, x = x * x % MOD; 
	}
	return res;
} 

void solve1() {
	for(int i = 1, x, y, z; i <= m; i++) {
		x = read(); y = read(); z = read();
		a[x][x] = (a[x][x] + z) % MOD;
		a[y][y] = (a[y][y] + z) % MOD;
		a[x][y] = (a[x][y] - z + MOD) % MOD;
		a[y][x] = (a[y][x] - z + MOD) % MOD;
	}
	--n;
	LL res = 1;
	for(int i = 1; i <= n; i++) {
		for(int j = i; j <= n; j++)
		 if(a[j][i]) {
		 	if(i != j) swap(a[i], a[j]), res *= -1;
		 	break;
		 }
		LL inv = expow(a[i][i], MOD - 2);
		for(int j = i + 1; j <= n; j++) {
			LL r = a[j][i] * inv % MOD;
			for(int k = i; k <= n; k++)
			 a[j][k] = (a[j][k] - a[i][k] * r % MOD + MOD) % MOD;
		}
	}
	for(int i = 1; i <= n; i++) res = res * a[i][i] % MOD;
	printf("%lld\n",(res + MOD) % MOD);
}

void solve2() {
	for(int i = 1, x, y, z; i <= m; i++) {
		x = read(); y = read(); z = read();
		a[y][y] = (a[y][y] + z) % MOD;
		a[x][y] = (a[x][y] - z + MOD) % MOD;
	}
	LL res = 1;
	for(int i = 2; i <= n; i++) {
		for(int j = i; j <= n; j++)
		 if(a[j][i]) {
		 	if(i != j) swap(a[i], a[j]), res *= -1;
		 	break;
		 }
		LL inv = expow(a[i][i], MOD - 2);
		for(int j = i + 1; j <= n; j++) {
			LL r = a[j][i] * inv % MOD;
			for(int k = i; k <= n; k++)
			 a[j][k] = (a[j][k] - a[i][k] * r % MOD + MOD) % MOD;
		}
	}
	for(int i = 2; i <= n; i++) res = res * a[i][i] % MOD;
	printf("%lld\n",(res + MOD) % MOD);
}

int main() {
	n = read(); m = read(); t = read();
	if(t == 0) solve1();
	else if(t == 1) solve2();
	return 0;
}

P4336 [SHOI2016]黑暗前的幻想乡

题目大意

\(n\) 个城市和 \(m\) 条双向道路,现在需要维修其中恰好 \(n-1\) 条,使任意两个城市能通过维修的道路互相到达。有 \(n-1\) 个工人,每个工人可以修他愿意修的若干条道路。要求每个工人恰好修一条路,求方案数。

Solution

考虑容斥,枚举 \(k\) 个工人参加,加入这 \(k\) 个工人愿意修的边。最后的答案乘上系数 \((-1)^{n-k-1}\)

P3317 [SDOI2014]重建

题目大意

\(n\) 个城市和 \(m\) 条双向道路,有 \(p_i\) 的概率第 \(i\) 条边损害。求最后剩下恰好 \(n-1\) 条边且城市可以两两互相到达的概率。

Solution

考虑带边权的矩阵树定理求出的值为

\[\sum_{T \in S} \prod_{e \in T}e \]

而本题要求的是

\[\sum_{T \in S} (\prod_{e \in T}p_e \prod_{e \notin T}(1-p_e)) \]

简单化一下式子

\[\begin{align*} & \quad \sum_{T \in S} (\prod_{e \in T}p_e \prod_{e \notin T}(1-p_e))\\ & = \sum_{T \in S} \prod_{e \in T}p_e \frac{\prod (1 - p_e)}{\prod_{e \in T}(1 - p_e)}\\ & = \prod (1 - p_e) \sum_{T \in S} \prod_{e \in T} \frac{p_e}{1 - p_e} \end{align*}\]

P4455 [CQOI2018]社交网络

题目大意

\(n\) 个朋友存在单向的认识关系。每当一个人发出消息,所有认识他的人都会看到他的消息。一个人可能同时看到多个人的消息,但他只能转发一个人的消息。1 号会发出一条消息,要使每个人都能看到这条消息,转发方案一共有多少种。

Solution

即求有向图以 1 为根的外向树或内向树。(只能统计一种)

Code

#include<cstdio>
#include<iostream>
using namespace std;

inline int read() {
	int res = 0, f = 1; char ch;
	while((ch = getchar()) && (ch < '0' || ch > '9') && ch != '-');
	(ch == '-') ? f = -1 : res = ch - '0';
	while((ch = getchar()) && ch >= '0' && ch <= '9') res = (res << 3) + (res << 1) + ch - '0';
	return res * f;
}

const int MAXN = 255, MOD = 1e4 + 7;

int n, m, a[MAXN][MAXN];

inline int expow(int x, int y) {
	int res = 1;
	while(y) {
		if(y & 1) res = res * x % MOD;
		y >>= 1, x = x * x % MOD;
	}
	return res;
}

int main() {
	n = read(); m = read();
	for(int i = 1, x, y; i <= m; i++) {
		x = read(); y = read();
		//y->x
		a[x][x] = (a[x][x] + 1) % MOD;
		a[y][x] = (a[y][x] - 1 + MOD) % MOD;
	}
	int res = 1;
	for(int i = 2; i <= n; i++) {
		for(int j = i; j <= n; j++)
		 if(a[i][j]) {
		 	if(i != j) swap(a[i], a[j]), res *= -1;
		 	break;
		 }
		int inv = expow(a[i][i], MOD - 2);
		for(int j = i + 1; j <= n; j++) {
			int r = a[j][i] * inv % MOD;
			for(int k = i; k <= n; k++)
			 a[j][k] = (a[j][k] - a[i][k] * r % MOD + MOD) % MOD;
		}
	}
	for(int i = 2; i <= n; i++) res = res * a[i][i] % MOD;
	printf("%d\n",(res + MOD) % MOD);
	return 0;
}
posted @ 2022-02-06 21:52  zym417  阅读(371)  评论(0编辑  收藏  举报