https://blog.csdn.net/xyz32768/article/details/78574746
一个 n n 阶方阵(行数和列数相等的矩阵) 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 行。
最后原矩阵被消成一个上三角矩阵(对于每个 1≤i≤n 1 ≤ i ≤ n ,第 i i 行前 i−1 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=D−A 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 的所有子树。
我们可以把这个 n−1 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 。
所以, p1≠1 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 定理:
[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=D−A 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=D−A 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 发出的所有边的权值之和。
对于任意 u≠v u ≠ v , D[u,v]=0 D [ u , v ] = 0 。
C=D−A C = D − A 。
所有生成树的边权积之和等于 C C 的任意余子式 M[i,i] M [ i , i ] 的行列式。
[BZOJ3534][Sdoi2014]重建:
https://www.lydsy.com/JudgeOnline/problem.php?id=3534