用途
矩阵树一般用于生成树计数的问题,比如求一个无向图中生成树的个数。用矩阵树定理能极大地降低时间复杂度。
前置知识:行列式
此部分可粗略浏览,了解即可。
对于一个一阶行列式,可写作
det(a1,1)=a1,1det
\left(
\begin{matrix}
a_{1,1}
\end{matrix}
\right)=a_{1,1}det(a1,1)=a1,1
对于一个二阶行列式,可写作
det(a1,1a1,2a2,1a2,2)=a1,1a2,2−a1,2a2,1det
\left(
\begin{matrix}
a_{1,1} & a_{1,2}\\
a_{2,1} & a_{2,2}
\end{matrix}
\right)=a_{1,1}a_{2,2}-a{1,2}a_{2,1}det(a1,1a2,1a1,2a2,2)=a1,1a2,2−a1,2a2,1
对于一个三阶的行列式,可写作
det(a1,1a1,2a1,3a2,1a2,2a2,3a3,1a3,2a3,3)=a1,1a2,2a3,3+a1,2a2,3a3,1+a1,3a2,1a3,2−a1,3a2,2a3,1−a1,2a2,1a3,3−a1,1a2,3a3,2det
\left(
\begin{matrix}
a_{1,1} & a_{1,2} & a_{1,3}\\
a_{2,1} & a_{2,2} & a_{2,3}\\
a_{3,1} & a_{3,2} & a_{3,3}
\end{matrix}
\right)=a_{1,1}a_{2,2}a_{3,3}+a_{1,2}a_{2,3}a_{3,1}+a_{1,3}a_{2,1}a_{3,2}\\ \qquad\qquad \qquad \qquad\qquad\qquad-a_{1,3}a_{2,2}a_{3,1}-a_{1,2}a_{2,1}a_{3,3}-a_{1,1}a_{2,3}a_{3,2}deta1,1a2,1a3,1a1,2a2,2a3,2a1,3a2,3a3,3=a1,1a2,2a3,3+a1,2a2,3a3,1+a1,3a2,1a3,2−a1,3a2,2a3,1−a1,2a2,1a3,3−a1,1a2,3a3,2
通过观察,我们得到nnn阶行列式的一般定义。
令p1,p2,…,pnp_1,p_2,\dots,p_np1,p2,…,pn表示一个111到nnn的排列。若一个排列的逆序对数个数为偶数,则称这个排列为偶排列;若一个排列的逆序对数个数为奇数,则称这个排列为奇排列。
令
t(p1,p2,…,pn)={0,当p1,p2,…,pn是偶排列时1,当p1,p2,…,pn是奇排列时
t(p_1,p_2,\dots,p_n)=
\begin{cases}
0,&当p_1,p_2,\dots,p_n是偶排列时\\
1,&当p_1,p_2,\dots,p_n是奇排列时
\end{cases}
t(p1,p2,…,pn)={0,1,当p1,p2,…,pn是偶排列时当p1,p2,…,pn是奇排列时
则nnn阶行列式可以写成
det(a1,1a1,2⋯a1,na2,1a2,2⋯a2,n⋮⋮⋱⋮an,1an,2⋯an,n)=∑p1,p2,…pn(−1)t(p1,p2,…,pn)a1,p1a2,p2⋯an,pndet
\left(
\begin{matrix}
a_{1,1} & a_{1,2} & \cdots & a_{1,n}\\
a_{2,1} & a_{2,2} & \cdots & a_{2,n}\\
\vdots & \vdots & \ddots & \vdots\\
a_{n,1} & a_{n,2} & \cdots & a_{n,n}
\end{matrix}
\right)
=\sum\limits_{p_1,p_2,\dots p_n}(-1)^{t(p_1,p_2,\dots,p_n)}a_{1,p_1}a_{2,p_2}\cdots a_{n,p_n}deta1,1a2,1⋮an,1a1,2a2,2⋮an,2⋯⋯⋱⋯a1,na2,n⋮an,n=p1,p2,…pn∑(−1)t(p1,p2,…,pn)a1,p1a2,p2⋯an,pn
行列式也可写作
detA=det(a1,1a1,2⋯a1,na2,1a2,2⋯a2,n⋮⋮⋱⋮an,1an,2⋯an,n)=∣a1,1a1,2⋯a1,na2,1a2,2⋯a2,n⋮⋮⋱⋮an,1an,2⋯an,n∣=∣A∣det A=
det
\left(
\begin{matrix}
a_{1,1} & a_{1,2} & \cdots & a_{1,n}\\
a_{2,1} & a_{2,2} & \cdots & a_{2,n}\\
\vdots & \vdots & \ddots & \vdots\\
a_{n,1} & a_{n,2} & \cdots & a_{n,n}
\end{matrix}
\right)=
\left|
\begin{matrix}
a_{1,1} & a_{1,2} & \cdots & a_{1,n}\\
a_{2,1} & a_{2,2} & \cdots & a_{2,n}\\
\vdots & \vdots & \ddots & \vdots\\
a_{n,1} & a_{n,2} & \cdots & a_{n,n}
\end{matrix}
\right|=
|A|detA=deta1,1a2,1⋮an,1a1,2a2,2⋮an,2⋯⋯⋱⋯a1,na2,n⋮an,n=a1,1a2,1⋮an,1a1,2a2,2⋮an,2⋯⋯⋱⋯a1,na2,n⋮an,n=∣A∣
行列式的性质
性质1
设AAA是nnn阶矩阵,则detAT=detAdetA^T=detAdetAT=detA
性质2
行列式任意两行(或两列)互换,行列式变号。
推论: 行列式某两行(或两列)相同时,行列式值为000。
性质3
将一行(或一列)的每个数乘实数kkk,得到的行列式等于原来的行列式乘kkk。
推论: 行列式有两行(或两列)成比例,则行列式值为000。特别地,当行列式有一行(或一列)全为000时,行列式值为000。
性质4
∣a1,1+b1,1a1,2⋯a1,na2,1+b2,1a2,2⋯a2,n⋮⋮⋱⋮an,1+bn,1an,2⋯an,n∣=∣a1,1a1,2⋯a1,na2,1a2,2⋯a2,n⋮⋮⋱⋮an,1an,2⋯an,n∣+∣b1,1a1,2⋯a1,nb2,1a2,2⋯a2,n⋮⋮⋱⋮bn,1an,2⋯an,n∣ \left| \begin{matrix} a_{1,1}+b_{1,1} & a_{1,2} & \cdots & a_{1,n}\\ a_{2,1}+b_{2,1} & a_{2,2} & \cdots & a_{2,n}\\ \vdots & \vdots & \ddots & \vdots\\ a_{n,1}+b_{n,1} & a_{n,2} & \cdots & a_{n,n} \end{matrix} \right|= \left| \begin{matrix} a_{1,1}& a_{1,2} & \cdots & a_{1,n}\\ a_{2,1} & a_{2,2} & \cdots & a_{2,n}\\ \vdots & \vdots & \ddots & \vdots\\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \end{matrix} \right|+ \left| \begin{matrix} b_{1,1} & a_{1,2} & \cdots & a_{1,n}\\ b_{2,1} & a_{2,2} & \cdots & a_{2,n}\\ \vdots & \vdots & \ddots & \vdots\\ b_{n,1} & a_{n,2} & \cdots & a_{n,n} \end{matrix} \right|a1,1+b1,1a2,1+b2,1⋮an,1+bn,1a1,2a2,2⋮an,2⋯⋯⋱⋯a1,na2,n⋮an,n=a1,1a2,1⋮an,1a1,2a2,2⋮an,2⋯⋯⋱⋯a1,na2,n⋮an,n+b1,1b2,1⋮bn,1a1,2a2,2⋮an,2⋯⋯⋱⋯a1,na2,n⋮an,n
对列也相同。
推论: 将行列式任意一行(或一列)乘一个实数kkk再加到另一行(或另一列)上,行列式的值不变。
∣a1,1a1,2⋯a1,na2,1a2,2⋯a2,n⋮⋮⋱⋮an,1an,2⋯an,n∣=∣a1,1+k⋅aj,1a1,2+k⋅aj,2⋯a1,n+k⋅aj,na2,1a2,2⋯a2,n⋮⋮⋱⋮an,1an,2⋯an,n∣\left| \begin{matrix} a_{1,1} & a_{1,2} & \cdots & a_{1,n}\\ a_{2,1} & a_{2,2} & \cdots & a_{2,n}\\ \vdots & \vdots & \ddots & \vdots\\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \end{matrix} \right|= \left| \begin{matrix} a_{1,1}+k\cdot a_{j,1} & a_{1,2}+k\cdot a_{j,2} & \cdots & a_{1,n}+k\cdot a_{j,n}\\ a_{2,1} & a_{2,2} & \cdots & a_{2,n}\\ \vdots & \vdots & \ddots & \vdots\\ a_{n,1} & a_{n,2} & \cdots & a_{n,n} \end{matrix} \right|a1,1a2,1⋮an,1a1,2a2,2⋮an,2⋯⋯⋱⋯a1,na2,n⋮an,n=a1,1+k⋅aj,1a2,1⋮an,1a1,2+k⋅aj,2a2,2⋮an,2⋯⋯⋱⋯a1,n+k⋅aj,na2,n⋮an,n
行列式的计算方法
对于一个行列式
detA=∣a1,1a1,2⋯a1,na2,1a2,2⋯a2,n⋮⋮⋱⋮an,1an,2⋯an,n∣detA=
\left|
\begin{matrix}
a_{1,1} & a_{1,2} & \cdots & a_{1,n}\\
a_{2,1} & a_{2,2} & \cdots & a_{2,n}\\
\vdots & \vdots & \ddots & \vdots\\
a_{n,1} & a_{n,2} & \cdots & a_{n,n}
\end{matrix}
\right|detA=a1,1a2,1⋮an,1a1,2a2,2⋮an,2⋯⋯⋱⋯a1,na2,n⋮an,n
通过上述性质可将其变为上三角形式
detB=∣b1,1b1,2⋯b1,n0b2,2⋯b2,n⋮⋮⋱⋮00⋯bn,n∣detB=
\left|
\begin{matrix}
b_{1,1} & b_{1,2} & \cdots & b_{1,n}\\
0 & b_{2,2} & \cdots & b_{2,n}\\
\vdots & \vdots & \ddots & \vdots\\
0 & 0 & \cdots & b_{n,n}
\end{matrix}
\right|detB=b1,10⋮0b1,2b2,2⋮0⋯⋯⋱⋯b1,nb2,n⋮bn,n
设变化过程中交换行(或列)的次数为sss,则detA=detB=(−1)s∏i=1nbi,idetA=detB=(-1)^s\prod\limits_{i=1}^nb_{i,i}detA=detB=(−1)si=1∏nbi,i
也就是说,我们可以通过类似高斯消元的方式,用O(n3)O(n^3)O(n3)的时间复杂度来求一个nnn阶行列式的值。
矩阵树定理
有n(1≤n≤12)n(1\leq n\leq 12)n(1≤n≤12)个点,有一些点可以连边。求有多少种连边方案,可以使这nnn个点形成一棵树。
矩阵树定理介绍
对于一个无向图GGG,有以下几个概念:
- GGG的度数矩阵D[G]D[G]D[G]是一个n∗nn*nn∗n的矩阵,且满足di,j={0,i≠j点i的度数,i=jd_{i,j}= \begin{cases} 0,\qquad\qquad\quad i\neq j\\ 点i的度数,\quad i=j\quad \end{cases}di,j={0,i=j点i的度数,i=j
- GGG的邻接矩阵A[G]A[G]A[G]也是一个n∗nn*nn∗n的矩阵,且满足ai,j={1,点i和点j之间有边相连0,点i和点j之间没有边相连a_{i,j}= \begin{cases} 1,&点i和点j之间有边相连\\ 0,&点i和点j之间没有边相连\quad \end{cases}ai,j={1,0,点i和点j之间有边相连点i和点j之间没有边相连
我们定义GGG的KirchhoffKirchhoffKirchhoff矩阵C[G]C[G]C[G]为C[G]=D[G]−A[G]C[G]=D[G]-A[G]C[G]=D[G]−A[G],则GGG所有不同生成树的个数等于其KirchhoffKirchhoffKirchhoff矩阵C[G]C[G]C[G]的任意一个n−1n-1n−1阶主子式的行列式的绝对值。
n−1n-1n−1阶主子式: 对于r(1≤r≤n)r(1\leq r\leq n)r(1≤r≤n),将C[G]C[G]C[G]的第rrr行和第rrr列同时去掉后得到的新矩阵,表示为Cr[G]C_r[G]Cr[G]。
那么对于上面这道题,我们只需要求出其KirchhoffKirchhoffKirchhoff矩阵C[G]C[G]C[G],再求detC[G]det C[G]detC[G]的任意一个n−1n-1n−1阶主子式的行列式的绝对值即可。
举例
一下是一个555个点,666条边的无向图。
则其KirchhoffKirchhoffKirchhoff矩阵为
C[G]=∣2−1−100−120−10−103−1−10−1−13−100−1−12∣C[G]=
\left|
\begin{matrix}
2 & -1 & -1 & 0 & 0\\
-1 & 2 & 0 & -1 & 0\\
-1 & 0 & 3 & -1 & -1\\
0 & -1 & -1 & 3 & -1\\
0 & 0 & -1 & -1 & 2\\
\end{matrix}
\right|
C[G]=2−1−100−120−10−103−1−10−1−13−100−1−12
r=2r=2r=2时
C2[G]=∣2−100−13−1−10−13−10−1−12∣=2⋅∣3−1−1−13−1−1−12∣−(−1)⋅∣−100−13−1−1−12∣=2×8+1×(−5)=11C_2[G]=
\left|
\begin{matrix}
2 & -1 & 0 & 0\\
-1 & 3 & -1 & -1\\
0 & -1 & 3 & -1\\
0 & -1 & -1 & 2\\
\end{matrix}
\right|
\\
\\
=2\cdot
\left|
\begin{matrix}
3 & -1 & -1\\
-1 & 3 & -1\\
-1 & -1 & 2\\
\end{matrix}
\right|-(-1)\cdot
\left|
\begin{matrix}
-1 & 0 & 0\\
-1 & 3 & -1\\
-1 & -1 & 2\\
\end{matrix}
\right|=
2\times 8+1\times(-5)=11
C2[G]=2−100−13−1−10−13−10−1−12=2⋅3−1−1−13−1−1−12−(−1)⋅−1−1−103−10−12=2×8+1×(−5)=11
上图生成树的个数就是111111个。
我们可以用类似高斯消元的方法来求行列式的值具体操作见下面例题的代码。其时间复杂度为O(n3)O(n^3)O(n3)。
证明过程比较繁琐,这里不做记录。并且在做题过程中也不需要证明,有了解,学会运用即可。
例题
SPOJ104 Highways
题目大意
给你一个有nnn个点mmm条边的无向图,求使每两个点间有且只有一条路线有多少方案(即求生成树的个数)。
1≤n≤121\leq n\leq 121≤n≤12,有多组数据。
题解
这就是一道矩阵树定理的板题。在代码中,gauss()gauss()gauss()函数其实就是类似高斯消元的函数,利用辗转相除法,用O(nlogn)O(n\log n)O(nlogn)的时间复杂度来使第jjj行的第iii列的数变为000。因为还要枚举当前行iii和需要修改的行jjj,所以总时间复杂度为O(t⋅n3logn)O(t\cdot n^3\log n)O(t⋅n3logn),对于这道题来说是可以过的。
code
#include<bits/stdc++.h>
using namespace std;
int t,n,m,x,y;
long long ans,a[105][105];
void gauss(){
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
while(a[j][i]){
long long tp=a[i][i]/a[j][i];
for(int v=i;v<=n;v++) a[i][v]-=a[j][v]*tp;
for(int v=i;v<=n;v++) swap(a[i][v],a[j][v]);
}
}
}
}
int main()
{
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
for(int i=0;i<=100;i++){
for(int j=0;j<=100;j++) a[i][j]=0;
}
for(int i=1;i<=m;i++){
scanf("%d%d",&x,&y);
--a[x][y];--a[y][x];
++a[x][x];++a[y][y];
}
for(int i=1;i<=n;i++){
a[i][n]=a[n][i]=0;
}
--n;
gauss();
ans=1;
for(int i=1;i<=n;i++){
ans=ans*a[i][i];
}
if(ans<0) ans=-ans;
printf("%lld\n",ans);
}
return 0;
}