从入门到入土:矩阵树Matrix-Tree定理
参考blog
在正式介绍Matrix_Tree定理之前,我们需要一些前置知识
一些定义与定理
对于一个无向图 G G ,ta的生成树个数等于其基尔霍夫Kirchhhoff矩阵任何一个阶主子式的行列式的绝对值
所谓 N−1 N − 1 阶主子式就是对于任意的一个r,将矩阵的第 r r 行和第列同时删去得到的新矩阵
基尔霍夫Kirchhoff矩阵的一种求法:
基尔霍夫 Kirchhoff K= K i r c h h o f f K = 度数矩阵 D− D − 邻接矩阵 A A
基尔霍夫矩阵的具体求法
-
度数矩阵
D
D
:是一个的矩阵,其中
D[i][j]=0(i≠j),D[i][i]=i号点的度数
D
[
i
]
[
j
]
=
0
(
i
≠
j
)
,
D
[
i
]
[
i
]
=
i
号
点
的
度
数
邻接矩阵
A
A
:是一个的矩阵,其中
A[i][i]=0,A[i][j]=A[j][i]=i,j之间的边数
A
[
i
]
[
i
]
=
0
,
A
[
i
]
[
j
]
=
A
[
j
]
[
i
]
=
i
,
j
之
间
的
边
数
基尔霍夫
Kirchhoff K=
K
i
r
c
h
h
o
f
f
K
=
度数矩阵
D−
D
−
邻接矩阵
A
A
举个例子:
行列式det(K)求法
得到基尔霍夫矩阵后,随便去掉某一行某一列并计算出新矩阵的行列式,其绝对值即为生成树个数
其中
P
P
为的任意一个排列,
τ(P)
τ
(
P
)
表示排列
P
P
逆序对数
而那个求和的每一项可以看做是在矩阵中选出个数,这
N
N
个数不同行不同列
不大明白?
看一个形象的表示方法:
在这个的矩阵中,每一条线就代表着
K1,p1×K2,p2×...×KN,pN
K
1
,
p
1
×
K
2
,
p
2
×
.
.
.
×
K
N
,
p
N
那么
τ(P)
τ
(
P
)
又是什么呢?
主要看两数连线的方向了,方向为 \ 的,表示连线的两个数之间无逆序关系,方向为 / 的,表示连线的两个数之间有逆序关系
具体怎么计算呢?
对于选中的一个序列,把每一行选中的数依次与ta的上一行,上上一行直至第一行选中的数连线
若连线方向为 \ ,则无逆序关系,方向为 /,则累加一个逆序数
第一行因为没有上一行,所以逆序数为0
举个例子:
淡蓝色(青色,水蓝色,管你叫什么颜色)的线就是我们选中的排列
a1,4
a
1
,
4
没有上一行,逆序数=0
a2,1
a
2
,
1
与
a1,4
a
1
,
4
连线,逆序数=1
a3,3
a
3
,
3
与
a1,4,a2,1
a
1
,
4
,
a
2
,
1
连线,逆序数=1
a4,2
a
4
,
2
与
a1,4,a2,1,a3,3
a
1
,
4
,
a
2
,
1
,
a
3
,
3
连线,逆序数=2
τ(P)=1+1=2=4
τ
(
P
)
=
1
+
1
=
2
=
4
求和式共有
N!
N
!
项,暴力求解的复杂度为
O(N!)∗N
O
(
N
!
)
∗
N
复杂度太高,我们看一下有没有优化解法
行列式的性质
性质一 . 互换矩阵的两行(列),行列式变号
考虑对于原矩阵
K
K
,我们可以得到其行列式的求和式:
若交换某两行的位置后得到了
K′
K
′
矩阵,若写出其行列式的求和式,不难发现,如果不看符号位的变化,只看每一个乘积项,那么这两个的矩阵的行列式的求和式是完全相同的
我们把相同的乘积项移到对应的位置,如图示:
然而显然,两个矩阵的这一项对应的排列
P
P
和不同:
P :1 2 4 3
P
:
1
2
4
3
P′:3 2 4 1
P
′
:
3
2
4
1
那么
τ(P)=1,(−1)τ(P)=−1
τ
(
P
)
=
1
,
(
−
1
)
τ
(
P
)
=
−
1
τ(P′)=4,(−1)τ(P′)=1
τ
(
P
′
)
=
4
,
(
−
1
)
τ
(
P
′
)
=
1
是不是都是这样呢?
即原来是-1,现在是1;原来是1,现在是-1:交换任意逆序对变化量为奇数?
答案是肯定的,证明如下:
由此可知,逆序对数的变化量为奇数,即两个det()求和式的对应的每一项的符号位都相反,所以互换矩阵的两行(列),行列式变号
性质二 . 如果矩阵有两行(列)完全相同,则行列式为0
证明:由性质1可知,交换这两行,得到的矩阵和原来相同,但是又要变化符号,则行列式的值只能为0
性质三 . 如果矩阵的某一行(列)中的所有元素都乘以同一个数k,新行列式的值等于原行列式的值乘上数k
证明:把求和式的每一项都提出一个公因子
k
k
推论:如果矩阵的某一行(列)中的所有元素都有一个公因子k,则可以把这个公因子k提到行列式求和式的外面
性质四 . 如果矩阵有两行(列)成比例(比例系数k),则行列式的值为 0
证明:把其中一行提出一个公因数k,那么剩下的det()求和式所代表的矩阵中存在一行或一列完全相同,则值为0
性质五 . 如果把矩阵的某一行(列)加上另一行(列)的k倍,则行列式的值不变
证明:可以从求和式子的每一项的那一行的那个元素下手,
把求和式拆成两个
det()
d
e
t
(
)
求和式:
det1()
d
e
t
1
(
)
与原矩阵的行列式求法相同
det2()
d
e
t
2
(
)
所代表的矩阵中有两行成比例,比例系数为
k
k
,值为0
所以相比原来的行列式,值不变
优化行列式的求法
给出一个矩阵:
注意到这个是一个上三角矩阵
其行列式的值为对角线的乘积(同理下三角矩阵)
因为只有时,乘积项中才没有0出现
由性质四可得,采用高斯消元的方法,把矩阵消为一个上三角矩阵后,然后求出对角线的积,便是该矩阵的行列式的值
时间复杂度
O(N3)
O
(
N
3
)
注意
如果要求的矩阵不允许出现实数,且需要取模,则采用辗转相除的高斯消元法
时间复杂度多一个
O(logN)
O
(
l
o
g
N
)
讲完了?差不多了
总结一下:
求一个图的生成树个数,首先构造度数矩阵
D
D
和邻接矩阵
基尔霍夫矩阵K=D−A
基
尔
霍
夫
矩
阵
K
=
D
−
A
方便起见,我们去掉
K
K
的第N行和第N列,得到一个新矩阵
用高斯消元(取模情况下要使用辗转相除的高斯消元)得到新矩阵的上三角形
对角线乘积的绝对值就是生成树个数
什么是辗转相除的高斯消元?
我们再计算高斯消元的时候,如果模数是质数,那么除法逆元可以直接用费马小定理解决
模数不是质数呢?
我们求gcd的时候一个经典方法就是辗转相除法
同样的,我们对于矩阵中的两行,不断地把主元较大的那一行减去主元较小的那一行,最终一定有一行主元为0,也就完成了消元
但是减法的效率不够高,我们可以计算两个主元相除的商
一次性用主元较大的行直接减去主元较小的行乘上
x
x
复杂度
需要注意的是,我们在gauss的时候会交换行
由性质一可得:我们需要统计一下交换的次数
如果次数为奇数,那么最后的答案还要乘上模意义下的
−1
−
1
要代码?没问题
实际上就是在消元的时候变成了辗转相除
ll gauss(int n) {
int now,to;
int opt=0;
for (int i=1;i<=n;i++) {
for (to=now;to<=n;to++)
if (a[to][i]) break;
if (to>n) return 0; //无解
if (to!=now) {
for (int j=1;j<=n;j++)
swap(a[to][j],a[now][j]);
opt^=1;
}
for (int j=i+1;j<=n;j++)
while (a[j][i]!=0) {
int x=a[j][i]/a[now][i];
if (x!=0) {
for (int k=1;k<=n;k++) {
a[j][k]-=(ll)x*a[now][k];
a[j][k]=(a[j][k]%p+p)%p;
}
}
else {
for (int k=1;k<=n;i++) //系数比较小
swap(a[now][k],a[j][k]);
opt^=1;
}
}
}
ll ans=1;
for (int i=1;i<=n;i++)
ans=(ans*a[i][i])%p;
if (opt) ans=mod-ans;
return ans;
}
度数矩阵 D D :是一个的矩阵,其中
D[i][j]=0(i≠j),D[i][i]=i号点的度数 D [ i ] [ j ] = 0 ( i ≠ j ) , D [ i ] [ i ] = i 号 点 的 度 数邻接矩阵 A A :是一个的矩阵,其中
A[i][i]=0,A[i][j]=A[j][i]=i,j之间的边数 A [ i ] [ i ] = 0 , A [ i ] [ j ] = A [ j ] [ i ] = i , j 之 间 的 边 数基尔霍夫 Kirchhoff K= K i r c h h o f f K = 度数矩阵 D− D − 邻接矩阵 A A
举个例子:
行列式det(K)求法
得到基尔霍夫矩阵后,随便去掉某一行某一列并计算出新矩阵的行列式,其绝对值即为生成树个数
其中
P
P
为的任意一个排列,
τ(P)
τ
(
P
)
表示排列
P
P
逆序对数
而那个求和的每一项可以看做是在矩阵中选出个数,这
N
N
个数不同行不同列
不大明白?
看一个形象的表示方法:
在这个的矩阵中,每一条线就代表着
K1,p1×K2,p2×...×KN,pN
K
1
,
p
1
×
K
2
,
p
2
×
.
.
.
×
K
N
,
p
N
那么
τ(P)
τ
(
P
)
又是什么呢?
主要看两数连线的方向了,方向为 \ 的,表示连线的两个数之间无逆序关系,方向为 / 的,表示连线的两个数之间有逆序关系
具体怎么计算呢?
对于选中的一个序列,把每一行选中的数依次与ta的上一行,上上一行直至第一行选中的数连线
若连线方向为 \ ,则无逆序关系,方向为 /,则累加一个逆序数
第一行因为没有上一行,所以逆序数为0
举个例子:
淡蓝色(青色,水蓝色,管你叫什么颜色)的线就是我们选中的排列
a1,4
a
1
,
4
没有上一行,逆序数=0
a2,1
a
2
,
1
与
a1,4
a
1
,
4
连线,逆序数=1
a3,3
a
3
,
3
与
a1,4,a2,1
a
1
,
4
,
a
2
,
1
连线,逆序数=1
a4,2
a
4
,
2
与
a1,4,a2,1,a3,3
a
1
,
4
,
a
2
,
1
,
a
3
,
3
连线,逆序数=2
τ(P)=1+1=2=4 τ ( P ) = 1 + 1 = 2 = 4
求和式共有
N!
N
!
项,暴力求解的复杂度为
O(N!)∗N
O
(
N
!
)
∗
N
复杂度太高,我们看一下有没有优化解法
行列式的性质
性质一 . 互换矩阵的两行(列),行列式变号
考虑对于原矩阵
K
K
,我们可以得到其行列式的求和式:
若交换某两行的位置后得到了
K′
K
′
矩阵,若写出其行列式的求和式,不难发现,如果不看符号位的变化,只看每一个乘积项,那么这两个的矩阵的行列式的求和式是完全相同的
我们把相同的乘积项移到对应的位置,如图示:
然而显然,两个矩阵的这一项对应的排列
P
P
和不同:
P :1 2 4 3
P
:
1
2
4
3
P′:3 2 4 1
P
′
:
3
2
4
1
那么
τ(P)=1,(−1)τ(P)=−1
τ
(
P
)
=
1
,
(
−
1
)
τ
(
P
)
=
−
1
τ(P′)=4,(−1)τ(P′)=1
τ
(
P
′
)
=
4
,
(
−
1
)
τ
(
P
′
)
=
1
是不是都是这样呢?
即原来是-1,现在是1;原来是1,现在是-1:交换任意逆序对变化量为奇数?
答案是肯定的,证明如下:
由此可知,逆序对数的变化量为奇数,即两个det()求和式的对应的每一项的符号位都相反,所以互换矩阵的两行(列),行列式变号
性质二 . 如果矩阵有两行(列)完全相同,则行列式为0
证明:由性质1可知,交换这两行,得到的矩阵和原来相同,但是又要变化符号,则行列式的值只能为0
性质三 . 如果矩阵的某一行(列)中的所有元素都乘以同一个数k,新行列式的值等于原行列式的值乘上数k
证明:把求和式的每一项都提出一个公因子 k k
推论:如果矩阵的某一行(列)中的所有元素都有一个公因子k,则可以把这个公因子k提到行列式求和式的外面
性质四 . 如果矩阵有两行(列)成比例(比例系数k),则行列式的值为 0
证明:把其中一行提出一个公因数k,那么剩下的det()求和式所代表的矩阵中存在一行或一列完全相同,则值为0
性质五 . 如果把矩阵的某一行(列)加上另一行(列)的k倍,则行列式的值不变
证明:可以从求和式子的每一项的那一行的那个元素下手,
把求和式拆成两个
det()
d
e
t
(
)
求和式:
det1()
d
e
t
1
(
)
与原矩阵的行列式求法相同
det2()
d
e
t
2
(
)
所代表的矩阵中有两行成比例,比例系数为
k
k
,值为0
所以相比原来的行列式,值不变
优化行列式的求法
给出一个矩阵:
注意到这个是一个上三角矩阵
其行列式的值为对角线的乘积(同理下三角矩阵)
因为只有时,乘积项中才没有0出现
由性质四可得,采用高斯消元的方法,把矩阵消为一个上三角矩阵后,然后求出对角线的积,便是该矩阵的行列式的值
时间复杂度
O(N3)
O
(
N
3
)
注意
如果要求的矩阵不允许出现实数,且需要取模,则采用辗转相除的高斯消元法
时间复杂度多一个 O(logN) O ( l o g N )
讲完了?差不多了
总结一下:
求一个图的生成树个数,首先构造度数矩阵
D
D
和邻接矩阵
方便起见,我们去掉
K
K
的第N行和第N列,得到一个新矩阵
用高斯消元(取模情况下要使用辗转相除的高斯消元)得到新矩阵的上三角形
对角线乘积的绝对值就是生成树个数
什么是辗转相除的高斯消元?
我们再计算高斯消元的时候,如果模数是质数,那么除法逆元可以直接用费马小定理解决
模数不是质数呢?
我们求gcd的时候一个经典方法就是辗转相除法
同样的,我们对于矩阵中的两行,不断地把主元较大的那一行减去主元较小的那一行,最终一定有一行主元为0,也就完成了消元
但是减法的效率不够高,我们可以计算两个主元相除的商
一次性用主元较大的行直接减去主元较小的行乘上
x
x
复杂度
需要注意的是,我们在gauss的时候会交换行
由性质一可得:我们需要统计一下交换的次数
如果次数为奇数,那么最后的答案还要乘上模意义下的
−1
−
1
要代码?没问题
实际上就是在消元的时候变成了辗转相除
ll gauss(int n) {
int now,to;
int opt=0;
for (int i=1;i<=n;i++) {
for (to=now;to<=n;to++)
if (a[to][i]) break;
if (to>n) return 0; //无解
if (to!=now) {
for (int j=1;j<=n;j++)
swap(a[to][j],a[now][j]);
opt^=1;
}
for (int j=i+1;j<=n;j++)
while (a[j][i]!=0) {
int x=a[j][i]/a[now][i];
if (x!=0) {
for (int k=1;k<=n;k++) {
a[j][k]-=(ll)x*a[now][k];
a[j][k]=(a[j][k]%p+p)%p;
}
}
else {
for (int k=1;k<=n;i++) //系数比较小
swap(a[now][k],a[j][k]);
opt^=1;
}
}
}
ll ans=1;
for (int i=1;i<=n;i++)
ans=(ans*a[i][i])%p;
if (opt) ans=mod-ans;
return ans;
}