高斯消元
求解
行列式
今天测试的时候出了【NOI2007】生成树计数,然后彻底被提示坑了,用行列式只能做(40\%)的数据,正确的解法应该是矩阵乘法,但这个不在文章的讨论范围,本文主要讨论如何用高斯消元求解行列式
首先
要知道什么是行列式?(百度、维基)
其实我也不太懂,只知道是(n imes n)矩阵的标量。
只要了解了行列式的性质,就可以求值了。
一个行列式用:$ egin{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 ext 是指全排列的集合,sigma ext 就是一个全排列, 如果sigma的逆序对对数为偶数,则sgn=1,否则=-1)
例如:(上面的例子)
全排列 | 逆序对对数 | (sgn) | value |
---|---|---|---|
1 2 3 | 0 | 1 | (1 imes 5 imes 0=0) |
1 3 2 | 1 | -1 | (-1 imes 6 imes 8=-48) |
2 1 3 | 1 | -1 | (-2 imes 4 imes 0=0) |
2 3 1 | 2 | 1 | (2 imes 6 imes 7=84) |
3 1 2 | 2 | 1 | $3 imes 4 imes 8=96 $ |
3 2 1 | 3 | -1 | (-3 imes 5 imes 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)+kdet(B))
根据性质四得(kdet(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
", (ans*POW(dv, mod-2)%mod+mod)%mod);
return 0;
}