矩阵树定理讲解
矩阵树定理
前置知识-行列式
定义
对于一个n × n n \times n n × n 的矩阵,其行列式为,
det ( A ) = ∑ P ( − 1 ) μ ( P ) ∏ i = 1 n A ( i , p i ) \det(A) = \sum_{P}(-1)^{\mu(P)}\prod_{i = 1} ^ n A(i, p_i)
det ( A ) = P ∑ ( − 1 ) μ ( P ) i = 1 ∏ n A ( i , p i )
其中P P P 为1 − n 1 - n 1 − n 的一个排列, μ ( P ) \mu(P) μ ( P ) 为排列P P P 的逆序对个数。
性质
单位矩阵I I I 的行列式为1 1 1 , 上三角矩阵和下三角矩阵的行列式都是对角线乘积。
交换矩阵的两行,行列式变号(改变了奇数个逆序对)。
若某一行乘以k k k ,行列式乘以k k k 。
行的线性性。
∣ a + a ′ b + b ′ c d ∣ = ∣ a b c d ∣ + ∣ a ′ b ′ c d ∣ \begin{vmatrix} a + a'& b+ b' \\ c & d\end{vmatrix}
= \begin{vmatrix} a & b \\ c & d\end{vmatrix} +
\begin{vmatrix} a'& b' \\ c & d\end{vmatrix}
∣ ∣ ∣ ∣ ∣ a + a ′ c b + b ′ d ∣ ∣ ∣ ∣ ∣ = ∣ ∣ ∣ ∣ ∣ a c b d ∣ ∣ ∣ ∣ ∣ + ∣ ∣ ∣ ∣ ∣ a ′ c b ′ d ∣ ∣ ∣ ∣ ∣
有某两行一样的矩阵,行列式为0 0 0 。
粗略的证明:根据1,假设我们交换相同的两行,会使行列式变号, 但是交换后,行列式并没有发生改变,也就是行列式只能等于0 0 0
用矩阵的上一行加上另一行的倍数, 行列式不变。
证明:
∵ ∣ a b c ⋯ a b c ∣ = 0 = ∣ a b c ⋯ k a k b k c ∣ = 0 ∴ ∣ a b c ⋯ d + k a e + k b f + k c ∣ = ∣ a b c ⋯ k a k b k c ∣ + ∣ a b c ⋯ d e f ∣ = ∣ a b c ⋯ d e f ∣ \because \begin{vmatrix} a & b & c \\ & \cdots \\ a & b & c\end{vmatrix} = 0
=\begin{vmatrix} a & b & c \\ & \cdots \\ ka & kb & kc\end{vmatrix} = 0 \\
\therefore \begin{vmatrix} a & b & c \\ & \cdots \\ d + ka & e + kb &f + kc\end{vmatrix}
= \begin{vmatrix} a & b & c \\ & \cdots \\ ka & kb & kc\end{vmatrix} + \begin{vmatrix} a & b & c \\ & \cdots \\ d & e & f\end{vmatrix} = \begin{vmatrix} a & b & c \\ & \cdots \\ d & e & f\end{vmatrix}
∵ ∣ ∣ ∣ ∣ ∣ ∣ ∣ a a b ⋯ b c c ∣ ∣ ∣ ∣ ∣ ∣ ∣ = 0 = ∣ ∣ ∣ ∣ ∣ ∣ ∣ a k a b ⋯ k b c k c ∣ ∣ ∣ ∣ ∣ ∣ ∣ = 0 ∴ ∣ ∣ ∣ ∣ ∣ ∣ ∣ a d + k a b ⋯ e + k b c f + k c ∣ ∣ ∣ ∣ ∣ ∣ ∣ = ∣ ∣ ∣ ∣ ∣ ∣ ∣ a k a b ⋯ k b c k c ∣ ∣ ∣ ∣ ∣ ∣ ∣ + ∣ ∣ ∣ ∣ ∣ ∣ ∣ a d b ⋯ e c f ∣ ∣ ∣ ∣ ∣ ∣ ∣ = ∣ ∣ ∣ ∣ ∣ ∣ ∣ a d b ⋯ e c f ∣ ∣ ∣ ∣ ∣ ∣ ∣
消元法求行列式
首先高斯消元的过程中我们用到的是交换两行,行列式的值变号, 某一行乘上一个数,行列式乘上对应值,一行加上另一行的倍数,行列式不变,最后得到上三角矩阵后,把影响逆回去即可。
P7112 行列式求值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <bits/stdc++.h> using namespace std;#define int long long const int N = 610 ;int a[N][N];int n, p;signed main () { scanf ("%lld%lld" , &n, &p); for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= n; j++) scanf ("%lld" , &a[i][j]); int ans = 1 , f = 1 ; for (int i = 1 ; i <= n; i++) { for (int j = i + 1 ; j <= n; j++) { while (a[i][i]) { int div = a[j][i] / a[i][i]; for (int k = i; k <= n; k++) a[j][k] = (a[j][k] - div * a[i][k] % p + p) % p; swap (a[i], a[j]); f = - f; } swap (a[i], a[j]); f = -f; } } for (int i = 1 ; i <= n; i++) ans = (ans * a[i][i]) % p; ans = f * ans; printf ("%lld" , (ans + p) % p); return 0 ; }
概念
矩阵树定理(Matrix-tree Theorem) 是把图的生成树个数和矩阵行列式联系起来的定理。(有很大的用处!)
定义
首先设有无向图G = ( V , E ) G = (V, E) G = ( V , E ) , 有p p p 个顶, q q q 条边。
然后我们把G G G 的每条边任意指定一个方向, 这样就可以定义G G G 的关联矩阵(Incidence Matrix) M ( G ) M(G) M ( G ) , 为一个p × q p \times q p × q 的矩阵,
M i , j = { − 1 v i i s t h e s t a r t o f e j 1 v i i s t h e e n d o f e j 0 o r t h e r w i s e M_{i,j} =
\left\{
\begin{aligned}
& -1 & v_i \ is \ the \ start\ of \ e_j \\
& 1 & v_i \ is \ the \ end \ of \ e_j \\
& 0 & ortherwise
\end{aligned}
\right.
M i , j = ⎩ ⎪ ⎪ ⎨ ⎪ ⎪ ⎧ − 1 1 0 v i i s t h e s t a r t o f e j v i i s t h e e n d o f e j o r t h e r w i s e
接着定义G G G 的拉普拉斯矩阵(Laplacian Matrix) L ( G ) L(G) L ( G ) , 为一个p × p p \times p p × p 的矩阵,
L i , j = { − m i , j i ≠ j , m i , j e d g e b e t w e e n v i , v j d e g ( v i ) i = j L_{i, j} =
\left\{
\begin{aligned}
& -m_{i, j} &i \not = j \ ,m_{i,j} \ edge \ between \ v_i, v_j \\
& deg(v_i) &i = j
\end{aligned}
\right.
L i , j = { − m i , j d e g ( v i ) i = j , m i , j e d g e b e t w e e n v i , v j i = j
注意到M M M 与G G G 指定的方向有关, 对L L L 没有影响,举一个例子:
对应的关联矩阵M M M 为
∣ 1 0 0 − 1 − 1 − 1 − 1 − 1 0 0 0 0 1 1 0 0 1 0 0 1 ∣ \begin{vmatrix}1&0&0&-1&-1\\-1&-1&-1&0&0\\0&0&1&1&0\\0&1&0&0&1\end{vmatrix}
∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ 1 − 1 0 0 0 − 1 0 1 0 − 1 1 0 − 1 0 1 0 − 1 0 0 1 ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣
对应的拉普拉斯矩阵L L L 为
∣ 3 − 1 − 1 − 1 − 1 3 − 1 − 1 − 1 − 1 2 0 − 1 − 1 0 2 ∣ \begin{vmatrix}
3 & -1 & -1 & -1 \\
-1 & 3 & -1 & -1\\
-1 & -1 & 2 & 0\\
-1 & -1 & 0 & 2
\end{vmatrix}
∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ 3 − 1 − 1 − 1 − 1 3 − 1 − 1 − 1 − 1 2 0 − 1 − 1 0 2 ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣
另外L L L 的非主对角线上的元素不一定是− 1 -1 − 1 , G G G 中允许重边存在。
引理
Lemma 1
M M T = L M M ^T = L M M T = L ,其中M T M^T M T 表示M M M 的转置
证明:由定义易得:( M M T ) i , j = ∑ e k ∈ E M i , k M k , j T = ∑ e k ∈ E M i , k M j , k (MM^T)_{i,j} = \sum_{e_k \in E} M_{i,k} M ^T_{k,j} = \sum_{e_k \in E} M_{i,k}M_{j,k} ( M M T ) i , j = ∑ e k ∈ E M i , k M k , j T = ∑ e k ∈ E M i , k M j , k ,i = j i = j i = j 时就相当于连了几条边也就是入度,i ≠ j i \not = j i = j 时两点之间有连边的话其中必定是一个终点和一个起点,也就是对应的− m i , j -m_{i,j} − m i , j ,得证。
接下来引入一些M M M 的子矩阵,称为Reduce Incidence Matrix ,M 0 M_0 M 0 是去掉M M M 最后一行得到的( p − 1 ) × q (p - 1) \times q ( p − 1 ) × q 矩阵。
定义一个( p − 1 ) × ( p − 1 ) (p - 1) \times (p - 1) ( p − 1 ) × ( p − 1 ) 的矩阵M 0 [ S ] M_0[S] M 0 [ S ] ,其中集合S = { i 1 , i 2 , ⋯ , i p − 1 } ∈ { 1 , 2 , ⋯ , q } S = \{i _ 1, i_2, \cdots ,i_{p-1}\} \in \{1, 2, \cdots , q\} S = { i 1 , i 2 , ⋯ , i p − 1 } ∈ { 1 , 2 , ⋯ , q } q其实就是抽出M 0 M_0 M 0 中p − 1 p - 1 p − 1 列,得到的一个新的矩阵。
Lemma 2
令S S S 是边集E E E 的一个大小为p − 1 p - 1 p − 1 的子集,若G ′ = ( V , S ) G' = (V,S) G ′ = ( V , S ) 不构成生成树,则det M 0 [ S ] = 0 \det M_0[S] = 0 det M 0 [ S ] = 0 ,若G ′ G' G ′ 构成生成树,则det M 0 [ S ] = ± 1 \det M_0[S] = \pm 1 det M 0 [ S ] = ± 1 ,其中det \det det 表示矩阵的行列式
Lemma 3
Binet-Cauchy Theorem 设A = ( a i , j ) A = (a_{i,j}) A = ( a i , j ) 是一个m × n m \times n m × n 矩阵, B = ( b i , j ) B = (b_{i,j}) B = ( b i , j ) 是一个n × m n \times m n × m 矩阵,则有det ( A B ) = ∑ S ( det A [ s ] ) ( det B [ s ] ) \det (AB) = \sum_{S} (\det A[s])(\det B[s]) det ( A B ) = ∑ S ( det A [ s ] ) ( det B [ s ] ) , 其中S S S 大小为m m m ,且是{ 1 , 2 , ⋯ , n } \{ 1, 2, \cdots , n\} { 1 , 2 , ⋯ , n } 的子集。
Lemma 4
Matrix-tree Theorem 设图G = ( V , E ) G = (V, E) G = ( V , E ) , 拉普拉斯矩阵L L L , 则G G G 的生成树的个数等于det L 0 \det L_0 det L 0 , 其中L 0 L_0 L 0 是去掉L L L 第i i i 列第i i i 行得到的子矩阵
拓展
无向图
设G G G 是一个有n n n 个顶点的无向图,定义其度数矩阵为
D i , i ( G ) = d e g ( i ) , D i , j ( G ) = 0 , i ! = j D_{i,i}(G) = deg(i), \ D_{i, j}(G) = 0, \ i != j
D i , i ( G ) = d e g ( i ) , D i , j ( G ) = 0 , i ! = j
其邻接矩阵为
A i , j ( G ) = A j , i ( G ) = e ( i , j ) , i ≠ j A_{i, j}(G) = A_{j , i}(G) = e(i, j), i \not= j
A i , j ( G ) = A j , i ( G ) = e ( i , j ) , i = j
其拉普拉斯矩阵为
L ( G ) = D ( G ) − A ( G ) L(G) = D(G) - A(G)
L ( G ) = D ( G ) − A ( G )
有向图
设G G G 是一个有n n n 个顶点的有向图, 定义其出度矩阵为
D i i o u t ( G ) = d e g o u t ( i ) , D i j o u t = 0 , i ≠ j D^{out}_{ii}(G) = \mathrm{deg^{out}}(i), D^{out}_{ij} = 0, i\neq j
D i i o u t ( G ) = d e g o u t ( i ) , D i j o u t = 0 , i = j
类似的入度矩阵也这样定义
定义其邻接矩阵为
A i , j = e ( i , j ) , i ≠ j A_{i,j} = e(i, j), \ i \not = j
A i , j = e ( i , j ) , i = j
其拉普拉斯矩阵L o u t L^{out} L o u t 为
L o u t ( G ) = D o u t ( G ) − A ( G ) L^{out}(G) = D^{out}(G) - A(G)
L o u t ( G ) = D o u t ( G ) − A ( G )
L i n L^{in} L i n 为
L i n ( G ) = D i n ( G ) − A ( G ) L^{in}(G) = D^{in}(G) - A(G)
L i n ( G ) = D i n ( G ) − A ( G )
带权-乘积之和
对于带权无向图,若存在边u → v u\rightarrow v u → v ,边权为c c c , 则D x , x + c , D y , y + c , A x , y + c , A y , x + c D_{x, x} + c, D_{y,y} + c, A_{x, y} + c, A_{y, x} + c D x , x + c , D y , y + c , A x , y + c , A y , x + c 。删去任意一行和任意一列,求剩下的矩阵行列式即可。
对于带权有向图,若存在边u → v u \rightarrow v u → v ,边权为c c c ,外向树中D y , y + c D_{y, y} + c D y , y + c , 内向树中D x , x + c D_{x, x} + c D x , x + c , 内向树和外向树中A x , y + c A_{x, y} + c A x , y + c 。删去指定的根所在的行和列,求剩下的矩阵行列式即可。
P6178Matrix-Tree 定理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <bits/stdc++.h> using namespace std;#define ll long long const int N = 310 ;const int mod = 1e9 + 7 ;ll D[N][N], A[N][N], L[N][N]; int n, m, t;void add (int u, int v, int c, int type) { if (t == 0 ) { (D[u][u] += c) %= mod; (D[v][v] += c) %= mod; (A[u][v] += c) %= mod; (A[v][u] += c) %= mod; } else { (D[v][v] += c) %= mod; (A[u][v] += c) %= mod; } } ll ans; void gauss () { ll f = 1 ; for (int i = 2 ; i <= n; i++) { for (int j = i + 1 ; j <= n; j++) { while (L[i][i]) { ll div = L[j][i] / L[i][i]; for (int k = i; k <= n; k++) L[j][k] = ((L[j][k] - L[i][k] * div % mod) % mod + mod) % mod; swap (L[i], L[j]); f = -f; } f = -f; swap (L[i], L[j]); } } ans = f; for (int i = 2 ; i <= n; i++) ans = (ans % mod * (L[i][i] + mod) % mod + mod) % mod; } int main () { scanf ("%d%d%d" , &n, &m, &t); for (int i = 1 ; i <= m; i++) { int x, y, z; scanf ("%d%d%d" , &x, &y, &z); add (x, y, z, t); } for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= n; j++) L[i][j] = (D[i][j] - A[i][j] + mod) % mod; gauss (); printf ("%lld" , ans); return 0 ; }
带权-权值之和
上面的构造方式只是求了每个生成树边权乘积的和, 接下来需要解决,每个生成树边权之和的和,对于一条边权为w w w 的边,只需要将边权设为关于x x x 的多项式w x + 1 wx + 1 w x + 1 ,消元后最后的行列式的值的一次项系数就是答案, 对应的乘法改为( a + b x ) ( c + d x ) = a c + ( a d + b c ) x (a + bx) (c + dx) = ac + (ad + bc)x ( a + b x ) ( c + d x ) = a c + ( a d + b c ) x ,除法为a + b x c + d x = a c + b c − a d c 2 x \frac{a + bx} {c + dx} = \frac{a}{c} + \frac{bc - ad}{c ^ 2}x c + d x a + b x = c a + c 2 b c − a d x ,接下来证明一下构造方式的正确性,我们需要求的答案其实可以表示为每个边权× \times × 含有这条边权的生成树的个数,就可以构造一个多项式来求解,(老套路了),首先乘法的定义很好理解,说一下除法的,首先我们需要求的是C + D x = 1 c + d x C + Dx = \frac{1}{c + dx} C + D x = c + d x 1 ,即( C + D x ) ( c + d x ) = 1 m o d x 2 (C + Dx)(c + dx) = 1 \bmod x^2 ( C + D x ) ( c + d x ) = 1 m o d x 2 ,展开即可得到C = 1 c , D = − d c 2 C = \frac{1}{c}, D = -\frac{d}{c^2} C = c 1 , D = − c 2 d ,然后再乘上a + b x a + bx a + b x ,就可以得到新的式子为a c + b c − a d c 2 x \frac{a}{c} + \frac{bc - ad}{c^2}x c a + c 2 b c − a d x 。
P6624 作业题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 #include <bits/stdc++.h> using namespace std;#define ll long long const int N = 310 ;const int mod = 1e9 + 7 ;ll D[N][N], A[N][N], L[N][N]; int n, m, t;void add (int u, int v, int c, int type) { if (t == 0 ) { (D[u][u] += c) %= mod; (D[v][v] += c) %= mod; (A[u][v] += c) %= mod; (A[v][u] += c) %= mod; } else { (D[v][v] += c) %= mod; (A[u][v] += c) %= mod; } } ll ans; void gauss () { ll f = 1 ; for (int i = 2 ; i <= n; i++) { for (int j = i + 1 ; j <= n; j++) { while (L[i][i]) { ll div = L[j][i] / L[i][i]; for (int k = i; k <= n; k++) { L[j][k] = ((L[j][k] - L[i][k] * div % mod) % mod + mod) % mod; } swap (L[i], L[j]); f = -f; } f = -f; swap (L[i], L[j]); } } ans = f; for (int i = 2 ; i <= n; i++) ans = (ans % mod * (L[i][i] + mod) % mod + mod) % mod; } int main () { scanf ("%d%d%d" , &n, &m, &t); for (int i = 1 ; i <= m; i++) { int x, y, z; scanf ("%d%d%d" , &x, &y, &z); add (x, y, z, t); } for (int i = 1 ; i <= n; i++) for (int j = 1 ; j <= n; j++) L[i][j] = (D[i][j] - A[i][j] + mod) % mod; gauss (); printf ("%lld" , ans); return 0 ; }
后记
证明以后再补吧(逃