今天测试的时候出了【NOI2007】生成树计数,而后完全被提示坑了,用行列式只能作\(40\%\)的数据,正确的解法应该是矩阵乘法,但这个不在文章的讨论范围,本文主要讨论如何用高斯消元求解行列式php
要知道什么是行列式?(百度、维基)
其实我也不太懂,只知道是\(n \times n\)矩阵的标量。
只要了解了行列式的性质,就能够求值了。测试
一个行列式用:$ \begin{vmatrix} A \end{vmatrix} \(或\)\det(A)$表示,例如url
暂且把\(\det(A)\)的第\(i\)行第\(j\)列的数记做\(a_{ij}\),行列式的值为spa
\(S_n \text 是指全排列的集合,\sigma \text 就是一个全排列, 若是\sigma的逆序对对数为偶数,则sgn=1,不然=-1\)
例如:(上面的例子)code
全排列 | 逆序对对数 | \(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\) |
要知道行列式的一些性质,才能更好得求值
下面的相等均值行列式的值相等。blog
若 \(b_{ij}=a_{ji}\) ,则 \(\det(B)\) 为 \(\det(A)\) 的转置行列式
例如:\(\det(A)\)的转置行列式为ip
显然,对于原行列式的任意两个数,在新行列式的顺逆序关系没有变。例如 $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)\)
证毕。get
交换两行后,顺逆序关系会相反,例如 \(a_{ij},a_{pq}(i < p,j < q)\) ,交换后变成 \(a_{iq},a_{pj}(i < p,q > j)\) ,当 \(j > q\) 时也同样。因此选择了这一对数的全排列的值都要乘 \((-1)\) ,两行的数对可取尽整个行列式的全排列,因此行列式的值乘\((-1)\)。
证毕。string
由于行列式的值是全排列的值相加,而某一行的全部元素都乘以k至关于每一个全排列的值都乘以k,因此至关于整个行列式乘以k.
证毕。it
设\(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)\).(性质二)
二、假设处理到第\(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; }