前置知識
- \(\sum\) 為累加符號,\(\prod\) 為累乘符號。
- 上三角矩陣指只有對角線及其右上方有數值其餘都是 \(0\) 的矩陣。
- 如果一個矩陣的對角線全部為 \(1\) 那麼這個矩陣為單位矩陣記作 \(I\)。
- 對於矩陣 \(A_{n,m}\) 和矩陣 \(B_{m,n}\) 滿足 \(A_{i,j}=B_{j,i}\) 記作 \(A=B^T\)。
- 如果 \(i,j\in[1,n]\) 滿足 \(i<j\) 且 \(p_i>p_j\),那麼稱 \((p_i,p_j)\) 為一對逆序對。
- 假設 \(p\) 是一個排列,那麼 \(\tau(p)\) 為 \(p\) 中逆序對的個數。
定義
行列式 \(A\) 為 \(n\) 階方陣,那麼 \(|A|\) 為該矩陣的行列式,記作 \(\operatorname{det}(A)\)。
幾何意義
對於一個 \(n\) 維的向量空間中的 \(n\) 個向量,我們可以構造一個 \(n\) 階行列式。這個行列式的絕對值等於由這 \(n\) 個向量所構成的平行體的體積。
具體來說,如果我們有 \(n\) 個向量 \(\vec{v_1}, \vec{v_2}, ..., \vec{v_n}\),我們可以將這些向量的座標構造成一個n階行列式:
其中,\(v_{ij}\) 是向量 \(\vec{v_i}\) 的第 \(j\) 個座標。這個行列式的絕對值就是由向量 \(\vec{v_1}, \vec{v_2}, ..., \vec{v_n}\) 所形成的平行體的體積。
求解
暴力
首先有一個粗暴的做法單純是根據定義求解的,雖然無法應用到那時有助於理解定義。
要求解行列式首先需要隨機選擇一行,為了方便說明不妨取第一行。那麼在這一行的第 \(i\) 個元素的貢獻為 \((-1)^{1+i}\times 1\times\) 不看這一行和這一列剩餘的矩陣。
透過這個操作,我們就將這個行列式降階了,接下來我們只需要一直進行遞迴操作直到行列式的階成為 \(1\) 就好了。所以根據定義我們就可以在 \(O(n!)\) 的時間複雜度內求解出 \(n\) 階行列式的值了。
最佳化
假設矩陣 \(A\) 是一個上三角矩陣,那麼 \(\operatorname{det}(A)=\prod_{i=1}^{n}a_{i,i}\) 的值,求解的時間複雜度十分優秀為 \(O(n)\),考慮是否可以使用高斯消元進行最佳化。
排列的性質
- 定義如果 \(\tau(p)\) 為奇數那麼 \(p\) 為奇排列,反之即為偶排列。
- 對於一個 \(n(n\geq2)\) 階排列的所有排列情況,奇排列與偶排列的情況各有 \(\dfrac{1}{2}\cdot n!\) 種。
- 將 \(p\) 中兩個不同的元素進行交換得到一個新的排列的過程叫對換操作,進行一次對換操作會改變序列的奇偶性。
矩陣性質
- \(\operatorname{det}(A)=\operatorname{det}(A^T)\),所以說所有的對列成立的性質均對行成立,反之亦然。
帶入排列的性質自行觀察即可以得到。 - 交換某 \(2\) 行或列,此時的 \(\operatorname{det}(A)\) 需要乘以 \(-1\)。
證明同上。 - 根據上一行進行推論,如果有兩行相同那麼 \(\operatorname{det}(A)=0\)。
假設 \(s=\operatorname{det}(A)\),不妨設行 \(x\) 與行 \(y\) 相等,那麼假設交換 \(x,y\) 則有 \(\operatorname{det}(A)=-s\) 但是矩陣並未變化,所以 \(\operatorname{det}(A)=-\operatorname{det}(A)\) 就得到了 \(\operatorname{det}(A)=0\) 是唯一解了。 - 將 \(A\) 的一行全部乘以 \(k\),那麼 \(\operatorname{det}(A)\) 也需要乘以 \(k\)。
考慮從使用定義求解行列式的值的角度進行解釋。因為在求值是選擇任意行計算的結果都是相同的,所以不妨假設我們剛好選擇了全部乘以 \(k\) 的那一行,使用乘法分配律將 \(k\) 提出即可證明。 - 根據上一行進行推論,如果有一行全部為 \(0\) 那麼,那麼 \(\operatorname{det}(A)=0\)。
假設 \(s=\operatorname{det}(A)\),那麼不妨假設 \(x\) 行全部為 \(0\)。將行 \(x\) 全部乘以 \(k\) 得到 \(\operatorname{det}(A)=s\cdot k\),可是矩陣並未變化所以 \(\operatorname{det}(A)=s\),因為 \(k\) 為任意值所以得到 \(\operatorname{det}(A)=0\)。 - 如果行列式對應矩陣 \(A\) 中有一行,是對應 \(2\) 個矩陣 \(B,C\) 中分別的 \(2\) 行所有元素之和,那麼有 \(\det(A)=\det(B)+\det(C)\)。
- 將某一行的的 \(k\) 倍加到另外一行,不影響 \(\operatorname{det}(A)\) 的值。
結合前面的一些性質即可證明,十分顯然。
高斯消元
考慮到將某一行的的 \(k\) 倍加到另外一行不影響 \(\operatorname{det}(A)\) 的值,可以直接使用高斯消元就可以求解了。同樣還是高斯消元,用一個變數記錄交換兩行引起的符號改變。因為將一行的 \(𝑘\) 倍加到另一行上不影響答案,可以採用輾轉相除的方式,將其他行的對應位置消成 \(0\)。
因為輾轉相除法帶一個 \(\log\),所以總的時間複雜度為 \(O(n^2\log W+n^3)\) 也就是 \(O(n^3)\),其中 \(W\) 為矩陣的值域。
AC Code
#define debug
// #define tests
#include<bits/stdc++.h>
#define int long long
#define x first
#define y second
using namespace std;
template<typename T=int> inline T read(){T x;cin>>x;return x;}
struct debug_{template<typename T>debug_&operator<<(T x){
#ifdef debug
cout<<x;
#endif
}}o;
const int N=605;
int n,a[N][N],mod;
void solve(){
cin>>n>>mod;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>a[i][j];
}
}
int flag=1;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
while(a[i][i]){
int s=a[j][i]/a[i][i];
for(int k=i;k<=n;k++){
a[j][k]=(a[j][k]-s*a[i][k]+mod)%mod;
}
flag++;
swap(a[i],a[j]);
}
flag++;
swap(a[i],a[j]);
}
}
int ans=1;
for(int i=1;i<=n;i++){
ans=(ans*a[i][i])%mod;
}
cout<<(ans*(flag%2?1:-1)+mod)%mod;
}
signed main(){
#ifdef debug
#else
ios::sync_with_stdio(false),cin.tie(nullptr);
#endif
int T=1;
#ifdef tests
cin>>T;
#endif
while(T--) solve();
return 0;
}