矩阵树定理学习笔记

前言

本篇博客仅针对定理的应用、实现进行总结,至于证明“前人之述备矣”,所以这里就不赘述了。我绝对不会告诉你,是因为博主又笨又懒不会证!

前置知识

  • 行列式

  • 高斯消元

  • 良好的心态( 茂密的头发

推荐证明


定理介绍

\(Kirchhoff\) 矩阵树定理(简称矩阵树定理)用于解决一张图的生成树个数计数问题。

定理:

对于⼀个⽆向图 \(G\) ,它的⽣成树个数等于其基尔霍夫 \(Kirchhoff\) 矩阵任何⼀个 \(N-1\) 阶主⼦式的⾏列式的绝对值。

\(★\): 在矩阵树中,无论有向无向边都允许重边,但是不允许自环的存在。

行列式较为重要的几个性质

  • 互换矩阵的两⾏(列),⾏列式变号;

  • 如果矩阵有两⾏(列)完全相同,则⾏列式为 \(0\);

  • 如果矩阵的某⼀⾏(列)中的所有元素都乘以同⼀个数 \(k\),新⾏列式的值等于原⾏列式的值乘上数 \(k\);

  • 如果矩阵有两⾏(列)成比例(⽐例系数 \(k\)),则⾏列式的值为 \(0\);

  • 如果把矩阵的某⼀⾏(列)加上另⼀⾏(列)的 \(k\) 倍,则⾏列式的值不变;

对于任意一个行列式,我们都可以通过以上性质将其转化为上三角下三角矩阵。其值就是对角线的乘积。因此,我们要用到高斯消元求值。

高斯消元大家都会吧? 那玩意儿跟我有仇,我是不会讲它的!!!

建图方法

\(Kirchhoff\) 矩阵 \(K\) = 度数矩阵 \(D\) - 邻接矩阵 \(A\);

具体构造:

  • 度数矩阵 \(D\):⼀个 \(N * N\) 的矩阵,其中 \(D[i][j] = 0 (i != j)\)\(D[i][i]\) \(=\) \(i\) 号点的度数

  • 邻接矩阵 \(A\):是⼀个 \(N * N\) 的矩阵,其中 \(A[i][i] = 0\)\(A[i][i] = 0\)\(A[i][j] = A[j][I]\) \(=\) \(i\),\(j\) 之间的边数。

无向图

对于无向图,我们可以直接写一个加边函数:

inline void add(int x,int y) {
	square[x][x] ++, square[y][y] ++;
	square[x][y] --, square[y][x] --;
}

有向图

而对于有向图,我们就要分情况而定了。

首先,先确定边统一的方向,到底是内向树还是外向树。

内/外向树,顾名思义嘛~ 内向树就是从外向根拓展,方向向内,外向树就是从根向外拓展,方向向外。感性理解一下啦~

这里引用command_block巨佬在博客中的介绍方法,

前面都是无向图,神奇的是有向图的情况也是可以做的。
(邻接矩阵 \(A\) 的意义同有向> 图邻接矩阵)
那么现在的矩阵 \(D\) 就要变一下了。
\(D[i][i] = \sum_{j=1}^n A[j][i]\),即到该点的边权总和(入)。
此时求的就是外向树 (从根向外)
\(D[i][i] = \sum_{j=1}^n A[i][j]\),即从从该点出发的边权总和(出)。
此时求的就是内向树 (从外向根)
此外,既然是有向的,那么就需要指定根。
前面提过要任意去掉第 \(k\) 行与第 \(k\) 列,是因为无向图所以不用在意谁为根。
在有向树的时候需要理解为指定根,结论是 : 去掉哪一行就是那一个元素为根。

所以,建边方式为:

  • 内向树
inline void add(int x,int y) { // x --> y
	square[y][y] ++, square[x][y] --;
}
  • 外向树
inline void add(int x,int y) { // x --> y
	square[x][x] ++, square[y][x] --;
}

带边权

把权值看作重边数,度数矩阵变成相邻边的权值和即可,具体证明自行百度或者看上面那位大佬的博客

这里就以无向图和内向树举例:

if(!opt) {//无向图
	sum[u][u] = (sum[u][u] + w) % mod;
	sum[v][v] = (sum[v][v] + w) % mod;
	sum[u][v] = (sum[u][v] - w + mod) % mod;
	sum[v][u] = (sum[v][u] - w + mod) % mod;
}
else {//内向图
	sum[v][v] = (sum[v][v] + w) % mod;
	sum[u][v] = (sum[u][v] - w + mod) % mod; 
}
}

掌握好建图技巧,就可以开启我们的征途啦~ ↖(ω)↗


下面是具体的分类例题讲解~

无向图模板类

小Z的房间

题目链接

矩阵树定理如下(上面提到过):

对于一个无向图 \(G\) ,它的生成树个数等于其基尔霍夫 \(Kirchhoff\) 矩阵任何一个 \(N-1\) 阶主子式的行列式的绝对值。

因为要求房子联通,所以就是要将房子看作点,如果两个房子相邻,就连边。

详细题解和代码点这里~

轮状病毒

题目链接

其实我的想法就是直接把病毒看做一个无向图,连好边之后用矩阵树定理就好了。

具体加边操作如下:

for(int i = 1; i <= n; i ++) add(i,n + 1);
//把中心点记作 n+1, 加的是内边。
for(int i = 1; i < n; i ++) add(i,i + 1);
//加外边
add(n,1);

但是这道题要高精,我,我不会懒得打,所以大家学思想就够了/doge

[中山市选]生成树

题目链接

题解啥的就先咕了吧,什么时候有时间了再补~

代码实现:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
const int mod = 2007;
int f,sum[405][405];
char c;
inline void read(int &x) {
	x = 0, f = 1, c = getchar();
	while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
	while(c <= '9' && c >= '0') x = x * 10 + c - '0', c = getchar();
	x *= f;
} 
inline int solve(int n) {
	int i,j,k,d;
	int ans = 1;
	for(i = 1; i <= n; i ++) {
		for(j = i + 1; j <= n; j ++) {
			while(sum[j][i]) {
				d = sum[i][i] / sum[j][i];
				for(k = 1; k <= n; k ++) sum[i][k] = (sum[i][k] - d * sum[j][k] % mod + mod) % mod, swap(sum[i][k],sum[j][k]);
				ans *= -1;
			}
		}
		ans = (ans * sum[i][i] % mod + mod) % mod;
	}
	return ans;
}
int main() {
	int t,n;
	scanf("%d",&t);
	while(t--) {
		scanf("%d",&n);
		n <<= 2;
		if(n == 8) {
			printf("40\n");
			continue;
		}
		memset(sum,0,sizeof(sum));
		for(int i = 1; i <= n; i ++) {
			sum[i][(i + 1) % n] = sum[(i + 1) % n][i] = -1;
			sum[i][i] = 2;
			if(i % 4 == 1) sum[i][i] = 4, sum[i][(i + 4) % n] = sum[(i + 4) % n][i] = -1;
		}
		printf("%d\n",solve(n - 1));
	}
	return 0;
}

推荐题目

有向图例题

\(It\) \(will\) \(be\) \(finished\) \(sometime\)......

posted @ 2021-02-06 21:05  Spring-Araki  阅读(122)  评论(0编辑  收藏  举报