行列式基础
行列式内容很多,这里只列出一些基础以及与矩阵树定理相关的内容
定义
矩阵的行列式,determinate(简称det)
是基于矩阵所包含的行列数据计算得到的一个标量。是为求解线性方程组而引入的。
二阶与三阶行列式
二阶与三阶是比较特殊的行列式
n阶行列式
观察上面3阶行列式的计算式特点
a
11
a
22
a
33
+
a
12
a
23
a
31
+
a
13
a
21
a
32
−
a
13
a
22
a
31
−
a
12
a
21
a
33
−
a
11
a
23
a
31
a_{11}a_{22}a_{33}+a_{12}a_{23}a_{31}+a_{13}a_{21}a_{32}-a_{13}a_{22}a_{31}-a_{12}a_{21}a_{33}-a_{11}a_{23}a_{31}
a11a22a33+a12a23a31+a13a21a32−a13a22a31−a12a21a33−a11a23a31
首先可以发现式子每一项都形如
a
1
p
1
a
2
p
2
a
3
p
3
a_{1p_1}a_{2p_2}a_{3p_3}
a1p1a2p2a3p3
即每个元素的第一个下标为标准排列,二第二个下表恰好取遍1~n的所有排列
再观察取值正负的特点
我们记一个排列
p
1
p
2
…
p
n
p_1p_2\dots p_n
p1p2…pn的逆序对数为
τ
(
p
1
p
2
…
p
n
)
\tau(p_1p_2\dots p_n)
τ(p1p2…pn)
若
τ
(
p
1
p
2
…
p
n
)
\tau(p_1p_2\dots p_n)
τ(p1p2…pn)为奇数,我们称排列
p
1
p
2
…
p
n
p_1p_2\dots p_n
p1p2…pn为奇排列,若为偶数则称偶排列
可以发现当
p
1
p
2
…
p
n
p_1p_2\dots p_n
p1p2…pn为奇排列时,对应的项取负号,相应的为偶排列时取正
那么n阶行列式计算式可表示为
∑
p
1
p
2
…
p
n
(
−
1
)
τ
(
p
1
p
2
…
p
n
)
a
1
p
1
a
2
p
2
…
a
n
p
n
\sum_{p_1p_2\dots p_n}(-1)^{\tau(p_1p_2\dots p_n)}a_{1p_1}a_{2p_2}\dots a_{np_n}
p1p2…pn∑(−1)τ(p1p2…pn)a1p1a2p2…anpn
行列式的性质
- Tip1:行列式与它的转置行列式相等
将行列式 D 的各行变成同序号的列后所得到的行列式称为 D 的转置行列式. 记为 D T D^T DT
- Tip2:互换行列式的两行(列),行列式变号
推论 :如果行列式有两行(列)完全相同,则此行列式为零
这条非常重要,接下来会用到
-
Tip3:若行列式的某一行(列)中所有的元素有公因数k,可以将这个k提出到行列式外
-
Tip4:行列式中如果有两行(列)元素成比例,则此行列式为零
-
Tip5:把行列式某一行(列)的个元素乘以同一个数之后加到另外一行(列)对应的元素上,行列式不变
特殊行列式计算
这个三角矩阵的计算也是待会要用到的
基尔霍夫(Kirchhoff)矩阵
假设有无向图
G
(
V
,
E
)
G(V,E)
G(V,E)
记
D
D
D为
G
(
V
,
E
)
G(V,E)
G(V,E)的度数矩阵,
D
i
,
i
D_{i,i}
Di,i表示结点
i
i
i的度数,显然这是一个对角矩阵
记
A
A
A为
G
(
V
,
E
)
G(V,E)
G(V,E)的邻接矩阵,
A
i
,
j
A_{i,j}
Ai,j表示结点
i
,
j
i,j
i,j间的边数
基尔霍夫 K K K表示为 K = D − A K=D-A K=D−A
矩阵树(Matrix-Tree)定理
取基尔霍夫矩阵的任意一个元素的余子式
通俗点讲就是去掉矩阵的任意一行一列
然后求出行列式的值,得到的就是在这个图中生成树的数量
根据上述行列式计算公式,其复杂度是
O
(
n
!
)
O(n!)
O(n!)的
所以考虑利用上述行列式的各种性质
可以用类似高斯消元的方式将原矩阵消成三角矩阵
再根据三角矩阵的计算式求解
dd gauss()
{
dd res=1.0;
for(int i=1;i<n;++i)
{
int r=i;
for(int j=i+1;j<n;++j)
if(fabs(a[j][i])>fabs(a[r][i])) r=j;
if(r!=i) swap(a[r],a[i]),res=-res;
if(fabs(a[i][i]<eps)) return 0;
for(int j=i+1;j<n;j++)
{
dd f=a[j][i]/a[i][i];
for(int k=i;k<n;k++)
a[j][k]-=a[i][k]*f;
}
}
for(int i=1;i<n;++i) res*=a[i][i];
return res;
}
当然还有很多时候计算行列式的时候需要取膜,那么就不能出现浮点数
可以利用辗转相除避免浮点数取膜的出现
int gauss()
{
int res=1;
for(int i=1;i<n;++i)//因为消去了一行一列,所以是i<n
{
for(int j=i+1;j<n;++j)
while(a[j][i])
{
int t=a[i][i]/a[j][i];
for(int k=i;k<n;++k)//Tip5
a[i][k]=(a[i][k]-t*a[j][k]+mod)%mod;
swap(a[i],a[j]);
res=-res;//Tip2:交换行列式两行,行列式变号
}
res=(res*a[i][i])%mod;
}
return (res+mod)%mod;
}
变元矩阵树定理
如果我们把邻接矩阵变成边权矩阵
即
A
i
,
j
A_{i,j}
Ai,j表示i,j两条边之间的边权,
D
i
,
i
D_{i,i}
Di,i表示与第i个节点的相连的边的边权和
那么得到的基尔霍夫矩阵行列式就是所有生成树中的边权之积的和
所以说上述矩阵树定理完全可以看做变元矩阵树定理的一种特殊情况
题目中应用更广泛的也还是 变元矩阵树定理
矩阵树定理应用
洛谷P4111 [HEOI2015]小Z的房间
练手裸题
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long lt;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int mod=1e9;
const int maxn=110;
int n,m;
int id[maxn][maxn],tot;
lt a[maxn][maxn];
char ss[maxn];
void add(int u,int v)
{
++a[u][u]; ++a[v][v]; --a[u][v]; --a[v][u];
}
int gauss()
{
lt res=1;
for(int i=1;i<tot;++i)
{
for(int j=i+1;j<tot;++j)
while(a[j][i])
{
lt t=a[i][i]/a[j][i];
for(int k=i;k<tot;++k)
a[i][k]=(a[i][k]-t*a[j][k]+mod)%mod;
swap(a[i],a[j]);
res=-res;
}
res=(res*a[i][i])%mod;
}
return (res+mod)%mod;
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;++i)
{
scanf("%s",ss);
for(int j=0;j<m;++j)
if(ss[j]=='.') id[i][j+1]=++tot;
}
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(id[i][j]){
if(id[i-1][j]) add(id[i][j],id[i-1][j]);
if(id[i][j-1]) add(id[i][j],id[i][j-1]);
}
printf("%d",gauss());
return 0;
}
洛谷P3317 [SDOI2014]重建
首先题目答案表达式
a
n
s
=
∑
T
r
e
e
(
∏
e
∈
T
r
e
e
p
e
∏
e
∉
T
r
e
e
(
1
−
p
e
)
)
ans=\sum_{Tree}(\prod_{e\in Tree}p_e\prod_{e\notin Tree}(1-p_e))
ans=∑Tree(∏e∈Treepe∏e∈/Tree(1−pe))
也就是所有树边出现的概率乘积 x 所有非树边不出现的概率乘积
直接将给定概率作为边权用矩阵树定理的话得到的是
∑
T
r
e
e
∏
e
∈
T
r
e
e
p
e
\sum_{Tree}\prod_{e\in Tree}p_e
∑Tree∏e∈Treepe
所以对式子稍作变换
∑ T r e e ( ∏ e ∈ T r e e p e ∏ e ∉ T r e e ( 1 − p e ) ) \sum_{Tree}(\prod_{e\in Tree}p_e\prod_{e\notin Tree}(1-p_e)) Tree∑(e∈Tree∏pee∈/Tree∏(1−pe))
∑ T r e e ( ∏ e ∈ T r e e p e ∏ e ( 1 − p e ) ∏ e ∈ T r e e ( 1 − p e ) ) \sum_{Tree}(\prod_{e\in Tree}p_e\frac{\prod_{e}(1-p_e)}{\prod_{e\in Tree}(1-p_e)}) Tree∑(e∈Tree∏pe∏e∈Tree(1−pe)∏e(1−pe))
∏ e ( 1 − p e ) ∑ T r e e ( ∏ e ∈ T r e e p e 1 − p e ) \prod_{e}(1-p_e)\sum_{Tree}(\prod_{e\in Tree}\frac{p_e}{1-p_e}) e∏(1−pe)Tree∑(e∈Tree∏1−pepe)
所以只要把原图每条边的边权看作
p
e
1
−
p
e
\frac{p_e}{1-p_e}
1−pepe再套用矩阵树定理即可
注意因为有除法计算,边权为0的边设为eps
#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
typedef double dd;
#define eps 1e-8
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=55;
int n;
dd a[maxn][maxn];
dd gauss()
{
dd res=1.0;
for(int i=1;i<n;++i)
{
int r=i;
for(int j=i+1;j<n;++j)
if(fabs(a[j][i])>fabs(a[r][i])) r=j;
if(r!=i) swap(a[r],a[i]),res=-res;
if(fabs(a[i][i]<eps)) return 0;
for(int j=i+1;j<n;j++)
{
dd f=a[j][i]/a[i][i];
for(int k=i;k<n;k++)
a[j][k]-=a[i][k]*f;
}
}
for(int i=1;i<n;++i) res*=a[i][i];
return res;
}
int main()
{
n=read();
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
scanf("%lf",&a[i][j]);
dd tt=1;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
{
if(fabs(a[i][j])<eps) a[i][j]=eps;
else if(fabs(1-a[i][j])<eps) a[i][j]=1-eps;
if(i<j) tt*=1.0-a[i][j];
a[i][j]=a[i][j]/(1.0-a[i][j]);
}
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(i!=j){
a[i][i]+=a[i][j];
a[i][j]=-a[i][j];
}
printf("%.4lf",gauss()*tt);
return 0;
}