[学习笔记]高斯消元、行列式、Matrix-Tree 矩阵树定理

一、前置芝士:高斯消元

https://blog.csdn.net/xyz32768/article/details/78574746

二、行列式的定义

一个 n n 方阵(行数和列数相等的矩阵) A A 的行列式为:

p1n(1)pi=1nA[i,pi] ∑ p 是 一 个 1 到 n 的 排 列 ( − 1 ) p 的 逆 序 对 数 ∏ i = 1 n A [ i , p i ]

记为 det(A) det ( A ) |A| | A |

三、行列式的性质

(1) |A|=|AT| | A | = | A T |
(2) |AB|=|A||B| | A B | = | A | | B |
(3)矩阵的一行乘上 k k 之后,行列式乘上 k k
(4)如果矩阵的每一行内数的和都为 0 0 ,每一列内数的和都为 0 0 ,那么行列式为 0 0
(5)交换矩阵的两行或两列,行列式取反。
(6)矩阵的一行减去另一行的 k k 倍,行列式不变。
性质(6)是求解行列式的关键。

四、求解行列式

暴力求解是 O(n2×n!) O ( n 2 × n ! ) 的。
利用高斯消元求解行列式。
根据性质(6),我们可以 for i i 1 1 n n ,用第 i i 行把所有满足 j[i+1,n] j ∈ [ i + 1 , n ] 的位置 (j,i) ( j , i ) 消成 0 0 ,也就是用第 i i 行去消第 j j 行。
最后原矩阵被消成一个上三角矩阵(对于每个 1in 1 ≤ i ≤ n ,第 i i 行前 i1 i − 1 个元素全部为 0 0 的矩阵)。
显然这时候矩阵的行列式为对角线上元素的乘积。
复杂度 O(n3) O ( n 3 )
代码:

double det(int n) {
    int i, j, k;
    For (i, 1, n) {
        int p = i;
        For (j, i + 1, n)
            if (fabs(mat[j][i]) > fabs(mat[p][i])) p = j;
        if (p != i) For (j, i, n) swap(mat[i][j], mat[p][j]);
        For (j, i + 1, n) {
            double tmp = mat[j][i] / mat[i][i];
            For (k, i, n) mat[j][k] -= mat[i][k] * tmp;
        }
    }
    double ans = 1;
    For (i, 1, n) ans *= mat[i][i];
    return ans;
}

如果行列式特别大且题目要求取模,则可以利用逆元:

int det(int n) {
    int i, j, k, res = 1;
    For (i, 1, n) {
        int qaq = qpow(a[i][i], ZZQ - 2);
        For (j, i + 1, n) {
            int tmp = 1ll * a[j][i] * qaq % ZZQ;
            For (k, 1, n) a[j][k] = (a[j][k] - 1ll * a[i][k] *
                tmp % ZZQ + ZZQ) % ZZQ;
        }
    }
    For (i, 1, n) res = 1ll * res * a[i][i] % ZZQ;
    return res;
}

如果模数不是质数,可以利用辗转相除的方法,复杂度多一个 log log
具体地,如果当前要用第 i i 行去消第 j j 行,那么:
(1)第 i i 行减掉第 j j 行的 A[i,i]A[j,i] ⌊ A [ i , i ] A [ j , i ] ⌋ 倍。这时候, A[i,i] A [ i , i ] 被消成 A[i,i]modA[j,i] A [ i , i ] mod A [ j , i ]
(2)交换第 i i 行和第 j j 行。注意,这个操作会导致行列式的取反,要用一个变量记录当前行列式是否被取反。
(3)如果 A[j,i]=0 A [ j , i ] = 0 ,我们就成功地用第 i i 行消去了第 j j 行。否则继续(1)(2)。
代码:

int solve() {
    int i, j, k; bool tr = 0;
    For (i, 1, n) For (j, i + 1, n) {
        int a = mat[i][i], b = mat[j][i];
        while (b) {
            int tmp = a / b; a %= b; swap(a, b);
            For (k, i, n) mat[i][k] = (mat[i][k] - 1ll * tmp * mat[j][k]
                % ZZQ + ZZQ) % ZZQ;
            For (k, i, n) swap(mat[i][k], mat[j][k]); tr ^= 1;
        }
    }
    int ans = 1; For (i, 1, n) ans = 1ll * ans * mat[i][i] % ZZQ;
    return tr ? (ZZQ - ans) % ZZQ : ans;
}

五、应用:生成树计数之矩阵-树定理

n n 阶无向图的一些定义:
邻接矩阵 A A :有边 (i,j) ( i , j ) A[i,j]=A[j,i]=1 A [ i , j ] = A [ j , i ] = 1 ,否则为 0 0 。特别地,如果图有重边,那么 A[i,j] A [ i , j ] A[j,i] A [ j , i ] 等于边 (i,j) ( i , j ) 的条数。
度数矩阵 D D D[i,i] D [ i , i ] 为点 i i 的度数,即由 i i 发出的边数。 D D 其他位置的数为 0 0
基尔霍夫矩阵: C=DA C = D − A
基尔霍夫矩阵每行内数的和和每列内的和都为 0 0 ,所以行列式为 0 0
余子式:一个矩阵 C C 的余子式 M[i,j] M [ i , j ] 表示 C C 去掉第 i i 行第 j j 列后得到矩阵的行列式。
矩阵-树定理,又称基尔霍夫定理。先说结论:
无向图的生成树个数等于其基尔霍夫矩阵 C C 的任意余子式 M[i,i] M [ i , i ] (注意是 M[i,i] M [ i , i ] 不是 M[i,j] M [ i , j ] )的行列式。
如果图不连通,那么任意余子式 M[i,i] M [ i , i ] 0 0
证明:如果图不连通,那么每个连通块内的点构成的矩阵仍然是基尔霍夫矩阵。
而连通块不止一个,所以去掉第 i i 行第 i i 列之后,一定有一个连通块仍然是基尔霍夫矩阵,也就是行列式为 0 0

六、证明

首先证明:一棵树,其基尔霍夫矩阵的任意余子式 M[i,i] M [ i , i ] 等于 1 1
为了方便讨论,我们交换第 1 1 行和第 i i 行,再交换第 1 1 列和第 i i 列,我们要证明的只是 M[1,1] M [ 1 , 1 ]
1 1 为根。
2 2 n n 的点重新排列,使同一个子树内的点编号形成一个区间。
我们会发现,把第 1 1 行第 1 1 列去掉,就相当于分离了 1 1 的所有子树。
我们可以把这个 n1 n − 1 阶方阵拆成 1 1 的度数个,每个小矩阵为一个子树的基尔霍夫矩阵,根为 r r 则矩阵第 r r 行第 r r 列加一。
易得 M[1,1] M [ 1 , 1 ] 为这些小矩阵的行列式之积(因为一个 a a b b 的排列和一个 b+1 b + 1 c c 的排列合并起来,相当于对应 A A 位置的乘积相乘,逆序对数相加)。
我们还要引入一个定理:
将基尔霍夫矩阵的第 1 1 行第 1 1 列凭空加上 1 1 之后,行列式等于 1 1
我们考虑用归纳法同时证明两个结论。
考虑行列式的式子中,每个排列 p p 对行列式的贡献。
证明第二个结论:
先讨论 p1=1 p 1 = 1 时,易得这对行列式的贡献为( 1 1 的度数加 1 1 )再乘以所有子树的(根所在主对角线位置加 1 1 后得到的行列式),也就是 1 1 的度数加 1 1
再考虑如果 p1=i p 1 = i ,那么 i i 一定是 1 1 的子节点。 i i 的取法数为 1 1 的度数。
分析一下可以得到,这时候 pi p i 必须为 1 1 ,否则对行列式的贡献为 0 0
这样看上去对行列式的贡献为 1 1 ,但是由于 p1=i p 1 = i pi=1 p i = 1 贡献了一个逆序对,对行列式的贡献为 1 − 1
所以, p11 p 1 ≠ 1 对行列式的贡献为 1 1 的度数)。
贡献相加一下就证明了结论二。
于是结论一也得证。

一起来证明 Matrix-Tree 定理。
先构建一个辅助矩阵 B B , 一个 m×n m × n 的矩阵, m m 为边数。
对于第 i i 条边 (u,v) ( u , v ) B[i,u]=1,B[i,v]=1 B [ i , u ] = 1 , B [ i , v ] = − 1
易得 BTB=C B T B = C
根据 Binet-Cauthy 定理:

|AB|=|S|=n|ASBS| | A B | = ∑ | S | = n | A S ∗ B ∗ S |

AS A S ∗ 表示 A A 只保留 向量集合 S S 后得到的矩阵。
AS A ∗ S 表示 A A 只保留 向量集合 S S 后得到的矩阵。
这个定理简单地说,一个 n×m n × m 的矩阵乘以一个 m×n m × n 的矩阵后的行列式,等于枚举一个大小为 n n 的集合 S S S{1,2,...,m} S ∈ { 1 , 2 , . . . , m } ,求 A A B B 分别只保留 S S 对应的列向量和行向量集合后的方阵乘积的行列式,并把所有 S S 对应求出的行列式加起来。
所以:
Ai,j A i , j 表示 A A 去掉第 i i 行第 j j 列后的子矩阵,
|Ci,i|=|S|=n1|BTN/A,i,SBi,N/A,S| | C i , i | = ∑ | S | = n − 1 | B N/A , i , S ∗ T B i , N/A , ∗ S |

=S=n1|Ci,i(S)| = ∑ S = n − 1 | C i , i ( S ) |

C(S) C ( S ) 表示边集 S S 构成的基尔霍夫矩阵。
讨论下这个式子。
相当于在 B B 中选出 n1 n − 1 条边。
(1)选出的边构成环或不连通。
实际上, n1 n − 1 条边连通 n n 个点的形态只能是树。
所以构成环和不连通其实是一样的。
由之前证出的定理得到贡献为 0 0
(2)选出的边构成树。
根据基尔霍夫矩阵的性质,得到贡献为 1 1
于是得证!!!!!
实现时注意自环。

七、题目 & 推广

[BZOJ4031][HEOI2015]小Z的房间:
https://www.lydsy.com/JudgeOnline/problem.php?id=4031
[BZOJ4596][Shoi2016]黑暗前的幻想乡:
https://www.lydsy.com/JudgeOnline/problem.php?id=4596
Alice:为什么我们要证明矩阵-树定理呢?
Bob:在应用中,矩阵-树定理有一些拓展。
下面给出两种常见拓展。
(1)有向图生成树计数。
①内向树生成树计数。
A A 为邻接矩阵, D D 出度矩阵。
C=DA C = D − A
root r o o t 为根的内向生成树个数就是 C C 的余子式 M[root,root] M [ r o o t , r o o t ] 的行列式。
②外向树生成树计数。
A A 为邻接矩阵, D D 入度矩阵。
C=DA C = D − A
root r o o t 为根的外向生成树个数就是 C C 的余子式 M[root,root] M [ r o o t , r o o t ] 的行列式。
[BZOJ5297][Cqoi2018]社交网络:
https://www.lydsy.com/JudgeOnline/problem.php?id=5297
(2)边带权。
给定一个边带权的图,求其所有生成树的边权积之和。
A A 为边权矩阵, D D 为发出边权和矩阵。
即对于一条边 (u,v) ( u , v ) ,边权为 w w A[u,v]=A[v,u]=w A [ u , v ] = A [ v , u ] = w
当然如果有重边, A[u,v] A [ u , v ] 为所有边 (u,v) ( u , v ) 的权值和。
D[u,u] D [ u , u ] 等于由 u u 发出的所有边的权值之和。
对于任意 uv u ≠ v D[u,v]=0 D [ u , v ] = 0
C=DA C = D − A
所有生成树的边权积之和等于 C C 的任意余子式 M[i,i] M [ i , i ] 的行列式。
[BZOJ3534][Sdoi2014]重建:
https://www.lydsy.com/JudgeOnline/problem.php?id=3534

你可能感兴趣的:(学习笔记)