行列式学习笔记
定义#
阶矩阵
的行列式为:
其中 取遍所有 阶排列。 定义为排列 的逆序对数目。
性质#
结论#
- 交换两行,行列式结果乘以 。
- 将一行乘以某个数加到另一行上时,行列式结果不变。
- 将某一行乘以 ,则行列式的值会乘以 。
- 矩阵转置(列变成行,行变成列),行列式不变。
证明#
以后再说。
应用#
【模板】行列式求值#
定义 阶矩阵 中的一个元素 的代数余子式 为 ,其中 是从 中抽掉第 行和第 列后得到的矩阵( 被称为余子式)。则有如下结论:
如果我们将最后一行中的数运用性质消成只剩最后一个数非零(即 ),容易发现 。而 是一个 阶矩阵,于是可以递归求解。由于每次取的都是 ,所以不用特殊处理。
消成最后一行只有最后一个数非零的方法是把每一列(设为第 列)都加上最后一列乘以 的值。
最后会得到一个上三角矩阵或者下三角矩阵,答案就是 。
下面的实现是照着第一篇题解写的。这种做法运用了辗转相减的技巧。意思是我们本来要直接 ,然后达到使得 的目的,但是直接这么做会导致精度或者逆元不存在的问题(这道题中是后者),所以我们一次只让两列变得尽量小,不断操作达到消元的目的。
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] 路径交点#
首先考虑 的情况。我们用一个二元组 表示一条连接第一层第 个点和第二层第 个点的路径。
考虑每一个 ,对于后面的一条路径 ,是否与其有交点(“后面的” 意为 )。显然只有 的时候有交点。然后发现这和逆序对很像,所以如果两层之间每一个点之间都有边,要求的就是一个 阶排列逆序对为偶数的情况减去逆序对为奇数的情况。
于是发现这又和行列式求值中的 很像,我们可以把两层之间的连边情况写成一个 阶矩阵。在行列式求值的式子中,枚举排列代表的就是第一层的点向哪一个点连边。显然我们要求如果出现不合法的情况后面那个式子就是 ,否则就是 ,于是就是邻接矩阵。
然后考虑 的情况。手玩一下会发现一个神奇的性质:仍然用一个排列 表示一条路径,其中起点为 的路径终点为 ,那么两条路径 只要有奇数个交点,必然有 ;只要有偶数个交点,必然有 ,与中途经过了那些点无关。证明也比较显然。
考虑构造这个矩阵,使 能够表示选择这种排列的方案数。发现由于路径要求不相交好像构造不出来?考虑问题的整体性,即使路径相交,将两个相交的路径的终点交换,会使逆序对的奇偶性发生变化,而同样的相交情况仍然会被枚举到,因此直接考虑所有方案就是对的(如果这里不能理解,可以画图思考或者去看题解中更详细的解释)。所以用 表示起点为 ,终点为 的路径的条数。这个矩阵可以用所有相邻两层的邻接矩阵按顺序相乘得到。容易发现最后的矩阵是 的,可以进行行列式求值。
这道题也可以用 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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现