Loading

行列式学习笔记

定义

\(n\) 阶矩阵

\[D=\begin{vmatrix} a_{1,1}&\cdots&,a_{1,n}\\ \vdots&\ddots&\vdots\\ a_{n,1}&\cdots&,a_{n,n} \end{vmatrix} \]

的行列式为:

\[\det(D)=|D|=\sum_{p}(-1)^{\tau(p)}\prod_{i=1}^na_{i,p_i} \]

其中 \(p\) 取遍所有 \(n\) 阶排列。\(\tau(p)\) 定义为排列 \(p\) 的逆序对数目。

性质

结论

  1. 交换两行,行列式结果乘以 \(-1\)
  2. 将一行乘以某个数加到另一行上时,行列式结果不变。
  3. 将某一行乘以 \(k\),则行列式的值会乘以 \(k\)
  4. \(|A\times B|=|A||B|\)
  5. 矩阵转置(列变成行,行变成列),行列式不变。

证明

以后再说。

应用

【模板】行列式求值

定义 \(n\) 阶矩阵 \(D\) 中的一个元素 \(a_{i,j}\) 的代数余子式 \(A_{i,j}\)\((-1)^{i+j}|M_{i,j}|\),其中 \(M_{i,j}\) 是从 \(D\) 中抽掉第 \(i\) 行和第 \(j\) 列后得到的矩阵(\(|M_{i,j}|\) 被称为余子式)。则有如下结论:

\[\begin{aligned} \forall 1\le i\le n,|D|=\sum_{j=1}^na_{i,j}A_{i,j}\\ \forall 1\le i,j\le n\land i\ne j,|D|=\sum_{k=1}^na_{k,i}A_{k,j} \end{aligned} \]

如果我们将最后一行中的数运用性质消成只剩最后一个数非零(即 \(i<n,a_{n,i}=0,a_{n,n}\ne 0\)),容易发现 \(|D|=a_{n,n}A_{n,n}\)。而 \(M_{n,n}\) 是一个 \(n-1\) 阶矩阵,于是可以递归求解。由于每次取的都是 \(A_{i,i}=(-1)^{2i}|M_{i,i}|=|M_{i,i}|\),所以不用特殊处理。

消成最后一行只有最后一个数非零的方法是把每一列(设为第 \(i\) 列)都加上最后一列乘以 \(-\dfrac{a_{n,i}}{a_{n,n}}\) 的值。

最后会得到一个上三角矩阵或者下三角矩阵,答案就是 \(\prod_{i=1}^na_{i,i}\)

下面的实现是照着第一篇题解写的。这种做法运用了辗转相减的技巧。意思是我们本来要直接 \(a_{j,k}\gets a_{j,k}-da_{i,k}\),然后达到使得 \(a_{j,i}=0\) 的目的,但是直接这么做会导致精度或者逆元不存在的问题(这道题中是后者),所以我们一次只让两列变得尽量小,不断操作达到消元的目的。

ll ret=1;
for(int i=1,d;i<=n;i++){// 求解 n 次,每次求解 n-i+1 阶的子矩阵
	for(int j=i+1;j<=n;j++){// 逐行消元,使得 (j,i) 变成 0,得到下三角矩阵
		while(a[i][i]){// 辗转相减
			d=a[j][i]/a[i][i];// 乘以第 i 行的系数
			for(int k=i;k<=n;k++){// 逐列消元
				a[j][k]=(a[j][k]-a[i][k]*d%P+P)%P;
			}
			swap(a[i],a[j]);ret=-ret;//交换两列行列式的值要乘以 -1
		}
		swap(a[i],a[j]);ret=-ret;
	}
}
for(int i=1;i<=n;i++)ret=(ret*a[i][i])%P;
return (ret%P+P)%P;

[NOI 2021] 路径交点

首先考虑 \(k=2\) 的情况。我们用一个二元组 \((x,y)\) 表示一条连接第一层第 \(x\) 个点和第二层第 \(y\) 个点的路径。

考虑每一个 \((x_1,y_1)\),对于后面的一条路径 \((x_2,y_2)\),是否与其有交点(“后面的” 意为 \(x_1<x_2\))。显然只有 \(y_1>y_2\) 的时候有交点。然后发现这和逆序对很像,所以如果两层之间每一个点之间都有边,要求的就是一个 \(n\) 阶排列逆序对为偶数的情况减去逆序对为奇数的情况。

于是发现这又和行列式求值中的 \((-1)^{\tau(p)}\) 很像,我们可以把两层之间的连边情况写成一个 \(n\) 阶矩阵。在行列式求值的式子中,枚举排列代表的就是第一层的点向哪一个点连边。显然我们要求如果出现不合法的情况后面那个式子就是 \(0\),否则就是 \(1\),于是就是邻接矩阵。

然后考虑 \(k>2\) 的情况。手玩一下会发现一个神奇的性质:仍然用一个排列 \(p\) 表示一条路径,其中起点为 \(i\) 的路径终点为 \(p_i\),那么两条路径 \(i<j\) 只要有奇数个交点,必然有 \(p_i>p_j\);只要有偶数个交点,必然有 \(p_i<p_j\),与中途经过了那些点无关。证明也比较显然。

考虑构造这个矩阵,使 \(\prod_{i=1}^na_{i,p_i}\) 能够表示选择这种排列的方案数。发现由于路径要求不相交好像构造不出来?考虑问题的整体性,即使路径相交,将两个相交的路径的终点交换,会使逆序对的奇偶性发生变化,而同样的相交情况仍然会被枚举到,因此直接考虑所有方案就是对的(如果这里不能理解,可以画图思考或者去看题解中更详细的解释)。所以用 \(a_{i,j}\) 表示起点为 \(i\),终点为 \(j\) 的路径的条数。这个矩阵可以用所有相邻两层的邻接矩阵按顺序相乘得到。容易发现最后的矩阵是 \(n_1\times n_k\) 的,可以进行行列式求值。

这道题也可以用 LGV 引理做,但是我不会。

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=205,P=998244353;
ll t[N][N];
inline ll solve(int n){
	ll d,ret=1;bool x=0;
	for(int i=1;i<=n;i++){
		for(int j=i+1;j<=n;j++){
			while(t[i][i]){
				d=t[j][i]/t[i][i];
				for(int k=i;k<=n;k++){
					t[j][k]=(t[j][k]-t[i][k]*d%P+P)%P;
				}
				swap(t[i],t[j]);x^=1;
			}
			swap(t[i],t[j]);x^=1;
		}
	}
	for(int i=1;i<=n;i++)ret=ret*t[i][i]%P;
	return x?-ret:ret;
}
inline void ct(){
	int K,n[N>>1],m[N>>1];
	ll e[N][N],s[N][N];
	memset(t,0,sizeof(t));
	for(int i=1;i<N;i++)t[i][i]=1;
	scanf("%d",&K);
	for(int i=1;i<=K;i++)scanf("%d",n+i);
	for(int i=1;i<K;i++)scanf("%d",m+i);
	for(int i=1,u,v;i<K;i++){
		memset(e,0,sizeof(e));
		memset(s,0,sizeof(s));
		for(int j=1;j<=m[i];j++){
			scanf("%d%d",&u,&v);e[u][v]=1;
		}
		for(int i1=1;i1<=n[1];i1++)for(int i2=1;i2<=n[i+1];i2++){
			for(int k=1;k<=n[i];k++)s[i1][i2]=(s[i1][i2]+t[i1][k]*e[k][i2]%P)%P;
		}
		memcpy(t,s,sizeof(t));
	}
	printf("%lld\n",(solve(n[1])%P+P)%P);
}
int main(){
	int T;scanf("%d",&T);
	while(T--)ct();
	return 0;
}
posted @ 2022-11-11 21:26  hihihi198  阅读(264)  评论(0)    收藏  举报