高斯消元
求解
行列式
今天測試的時候出了【NOI2007】生成樹計數,然后徹底被提示坑了,用行列式只能做\(40\%\)的數據,正確的解法應該是矩陣乘法,但這個不在文章的討論范圍,本文主要討論如何用高斯消元求解行列式
首先
要知道什么是行列式?(百度、維基)
其實我也不太懂,只知道是\(n \times n\)矩陣的標量。
只要了解了行列式的性質,就可以求值了。
一個行列式用:$ \begin{vmatrix} A \end{vmatrix} \(或\)\det(A)$表示,例如
暫且把\(\det(A)\)的第\(i\)行第\(j\)列的數記作\(a_{ij}\),行列式的值為
$$ \det(A) = \sum_{\sigma \in S_n} sgn(\sigma) \prod_{i=1}^n a_{i,\sigma(i)}$$
\(S_n \text 是指全排列的集合,\sigma \text 就是一個全排列, 如果\sigma的逆序對對數為偶數,則sgn=1,否則=-1\)
例如:(上面的例子)
全排列 | 逆序對對數 | \(sgn\) | value |
---|---|---|---|
1 2 3 | 0 | 1 | \(1 \times 5 \times 0=0\) |
1 3 2 | 1 | -1 | \(-1 \times 6 \times 8=-48\) |
2 1 3 | 1 | -1 | \(-2 \times 4 \times 0=0\) |
2 3 1 | 2 | 1 | \(2 \times 6 \times 7=84\) |
3 1 2 | 2 | 1 | $3 \times 4 \times 8=96 $ |
3 2 1 | 3 | -1 | \(-3 \times 5 \times 7=-105\) |
總和 | \(0-48+0+84+96-105=27\) |
要知道行列式的一些性質,才能更好得求值
下面的相等均值行列式的值相等。
性質一:
行列式與它的轉置行列式相等
若 \(b_{ij}=a_{ji}\) ,則 \(\det(B)\) 為 \(\det(A)\) 的轉置行列式
例如:\(\det(A)\)的轉置行列式為
顯然,對於原行列式的任意兩個數,在新行列式的順逆序關系沒有變。例如 $a_{ij} , a_{pq} (i < p) $ ,在 \(\det(B)\) 為 \(b_{ji},b_{qp}\)
若 \(j < q\) ,則兩數在 \(\det(A)\) 中為順序,在 \(\det(B)\) 中也是順序 \((j < q,i < q)\) .
若 \(j > q\) ,則兩數在 \(\det(A)\) 中為逆序,在 \(\det(B)\) 中也是逆序 \((q < j,p > i)\)
證畢。
性質二
交換行列式的兩行,行列式取相反數
交換兩行后,順逆序關系會相反,例如 \(a_{ij},a_{pq}(i < p,j < q)\) ,交換后變成 \(a_{iq},a_{pj}(i < p,q > j)\) ,當 \(j > q\) 時也一樣。所以選擇了這一對數的全排列的值都要乘 \((-1)\) ,兩行的數對可取盡整個行列式的全排列,所以行列式的值乘\((-1)\)。
證畢。
性質三
行列式的某一行的所有元素都乘以同一數k,等於用數k乘此行列式
因為行列式的值是全排列的值相加,而某一行的所有元素都乘以k相當於每個全排列的值都乘以k,所以相當於整個行列式乘以k.
證畢。
性質四
行列式如果有兩行元素成比例,則此行列式等於零
設\(a_i=ka_j\),將\(a_i變成a_j\),則整個行列式的值乘以k,行列式中有相等的兩行\(a_j\),交換相等的兩行\(a_j\),行列式的值取相反數,但行列式的元素並沒有改變,所以行列式的值為0.乘k依然為0.
證畢。
性質五
若行列式的某一行每一個元素都可以由兩個數相加得到,則這個行列式是對應兩個行列式的和。
舉個例子:
這個性質由乘法分配律可以容易得出,自行腦補。
性質六
把行列式的某一行的各元素乘以同一數然后加到另一行對應的元素上去,行列式不變
設\(a_i,a_j,\det(C)=\det(A)且c_j=kc_i+c_j,\det(B)=\det(A)且b_j=b_i\),則根據性質五得
\(\det(C)=\det(A)+k\det(B)\)
根據性質四得\(k\det(B)=0\),所以\(\det(C)=\det(A)\)
證畢。
以上所有性質在列上也適用
根據性質六,就可以用高斯消元解行列式了。
高斯消元沒什么好講的,就說一下要注意的細節吧。
1、高斯消元交換兩行時答案要除以\((-1)\).(性質二)
2、假設處理到第\(i\)個方程,一般的高斯消元是用第\(i\)個方程減去第\(j(j>i)\)個方程,所得的答案作為新的第\(j\)個方程,但求行列式的時候要用第\(j\)行減去第\(i\)行,所得答案作為新的第\(j\)行。因為方式一相當於某一行乘\((-1)\),另一行加到這一行上,這並不符合性質六。
然后答案就是主對角線的乘積。因為雖然高斯消元后的行列式為一個倒三角形,但可以按列來消,最后就只剩下主對角線了。或者這樣說,行列式為一個倒三角,用最原始的算值方法,最后一行一定要選第n個數,倒數第二行要選第n-1個數,以此類推,就把主對角線都乘起來了,如果不這樣選,答案就是0,對最終答案沒用貢獻。
貼代碼
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <queue>
#include <vector>
#include <map>
using namespace std;
const int mod=65521;
const int maxn=300;
typedef long long LL;
int n, m;
LL cnt[maxn][maxn];
LL ans, dv;
LL gcd(LL b, LL c)
{
if (c==0) return b;
else return gcd(c, b%c);
}
void init()
{
for (int i=1; i<n; ++i)
for (int j=1; j<n; ++j)
{
if (i==j) cnt[i][j]=(min(m, n-i)+min(m, i-1))%mod;
else
if (abs(i-j)<=m) cnt[i][j]=-1;
else cnt[i][j]=0;
}
}
void solve()
{
for (int i=1, j=1; i<n && j<n; ++i, ++j)
{
int id=i;
for (int k=i; k<n; ++k)
if (cnt[k][j]!=0) { id=k; break; }
if (cnt[id][j]==0) { --i; continue; }
if (id!=i)
{
ans*=-1;
for (int k=j; j<n; ++k) swap(cnt[id][k], cnt[i][k]);
}
for (int k=i+1; k<n; ++k)
if (cnt[k][j]!=0)
{
int tmp1=gcd(abs(cnt[i][j]), abs(cnt[k][j]));
int tmp2=cnt[i][j]/tmp1;
tmp1=cnt[k][j]/tmp1;
dv=dv*tmp2%mod;
for (int p=j; p<n; ++p)
cnt[k][p]=(cnt[k][p]*tmp2-cnt[i][p]*tmp1)%mod;
}
}
}
LL POW(LL b, LL c)
{
LL s=1, cur=b;
while (c)
{
if (c & 1) s=s*cur%mod;
cur=cur*cur%mod;
c>>=1;
}
return s;
}
int main()
{
freopen("count.in", "r", stdin);
freopen("count.out", "w", stdout);
scanf("%d%d", &m, &n);
init();
ans=dv=1;
solve();
for (int i=1; i<n; ++i) ans=ans*cnt[i][i]%mod;
printf("%I64d\n", (ans*POW(dv, mod-2)%mod+mod)%mod);
return 0;
}