行列式学习笔记

定义#

n 阶矩阵

D=|a1,1,a1,nan,1,an,n|

的行列式为:

det(D)=|D|=p(1)τ(p)i=1nai,pi

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

性质#

结论#

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

证明#

以后再说。

应用#

【模板】行列式求值#

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

1in,|D|=j=1nai,jAi,j1i,jnij,|D|=k=1nak,iAk,j

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

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

最后会得到一个上三角矩阵或者下三角矩阵,答案就是 i=1nai,i

下面的实现是照着第一篇题解写的。这种做法运用了辗转相减的技巧。意思是我们本来要直接 aj,kaj,kdai,k,然后达到使得 aj,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 个点的路径。

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

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

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

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

这道题也可以用 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 @   hihihi198  阅读(241)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示
主题色彩