矩阵树定理学习笔记
前言
本篇博客仅针对定理的应用、实现进行总结,至于证明“前人之述备矣”,所以这里就不赘述了。我绝对不会告诉你,是因为博主又笨又懒不会证!
前置知识
-
行列式
-
高斯消元
-
良好的心态(
茂密的头发
推荐证明
定理介绍
\(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\)......