updata on 2020.3.29
修了一些炸了的公式,原本在洛谷博客上是好的,搬过来成这样了
然后去掉了定理部分的列表,把一堆行间公式放在列表里是怎么想的。。。
然而#4和#5还是没填
on 2020.11.21
这篇后面两个内容耽误太久了,现在并不准备把它写完了,置顶也去掉了
发现那个奇怪的高斯消元方法好像是假的,删掉了它
1.排列和逆序对
1.1排列
将n个数按任意顺序排序,得到长度为n的排列
显然有\(n!\)种不同排列
在一个排列种,对换其中两数,其他数不动,的得到另一个排列,叫做对换
1.2逆序对
对于排列\(a_1,a_2,\dots a_n\),\((i,j),i<j,\text{且}a_i>q_j\)称为一个逆序对
好了可以认为以上都是废话
1.3
奇排列:逆序对数量是奇数
偶排列:逆序对数量是偶数
一些定理:
对换改变排列的奇偶性
设对换\(a_i,a_j(i\leq j)\)两个元素
考虑相邻的两个元素对换,逆序对数量\(+1\text{或}-1\),改变排序奇偶性
我们对换\(a_i,a_j\)时每次都只对换相邻的两个元素
\(a_i,a_{i+1},\dots,a_j\)
\(\rightarrow a_{i+1},a_i,a_{i+2},\dots ,a_j\)
\(\rightarrow a_{i+1},a_{i+2},a_i,a_{i+3},\dots,a_j\)
\(\dots\)
\(\rightarrow a_{i+1},a_{i+2},\dots a_j,a_i\)
然后再用相同的方法把\(a_j\)向左移动
移动\(i\)时移动\(j-i\)次,移动\(j\)要\(j-i-1\)次
所以共移动\(2(j-i)-1\)次,奇数
改变奇数次奇偶性,所以奇偶性肯定被改变
在\(n>1\)的排列中,奇偶排列各占一半
尝试将奇偶排列一一对应
设\(a_1,a_2,\dots,a_n\)为奇排列
则\(a_2,a_1,a_3,\dots,a_n\)为偶排列
所以建立起这种一一对应的关系,就可以证明奇偶排列数量相等
任意排列可以经给一系列对换变成自然排列,所作对换次数的奇偶性与这个排列的奇偶性相同
构造一种对换顺序,将1换到\(a_1\)上,2交换到\(a_2\)上,\(\dots\)
对换次数那个就不证了严格怎么证不大会
然而这些定理行列式种用到也不多
2.行列式
2.1定义
终于进入正题
n阶行列式由\(n^2\)个数通过下式确定一个数
当然看到这个式子很是懵
叙述一遍:枚举\(1\)到\(n\)的全排列(\(j_1,j_2,\dots,j_n\)),对于每一种排列,求\(a_{1,j_1}a_{2,j_2}\dots a_{n,j_n}\)再乘:
\(sgn(j_1j_2\dots j_n)=
\begin{cases}
1&j_1j_2\dots j_n\text{是偶排列}\\
-1&j_1j_2\dots j_n\text{是奇排列}\\
\end{cases}
\)
然后还是很懵
其实就是每行取一个数,每列取一个,乘起来,再乘那个\(sgn\)
可以自己找个例子试试
要注意矩阵和行列式是不同的,行列式就是一个数,矩阵是数表
显然,按照定义直接求行列式的值是\(O(n!n)\)
2.2一堆定理
2.2.1行列互换,值不变
比如
变成:
在取数的时候,\(\sum\)中的每一项都是只在每行取一个数,每列取一个
所以无论从行还是列去看,都是取了所有数
然后这个有一种“对称的”感觉
感性理解
2.2.2用一个数去乘某个行列式等于用这个数乘此行列式的某一行
比如这个数是\(k\)乘在第\(i\)行里
则:
在那个\(\sum\)中的每一项肯定都会乘一个第\(i\)行的数,所以把这个第\(i\)行的数中的\(k\)提出了,就是\(k\times \text{这个行列式乘k以前的值}\)
2.2.3如果行列式中某一行是两组数之和,则这个行列式等于分别以这两组数为改行,其余行与这个行列式相等的两个行列式之和
即:
在等号左边的行列式等于\(\sum{\dots\times (b_{i,j}+c_{i,j})}\)简写了
把那个\(\dots\)乘进去,就是\(\sum{\dots\times b_{i,j}}\)和\(\sum{\dots\times c_{i,j}}\),分别对应等号右边的两个行列式
2.2.4交换行列式两行,行列式符号改变
设把\(i,j\)两行交换
则改变的只有\(sgn(j_1j_2\dots j_n)\),根据我们在排列中的定理,做一次对换奇偶性改变,则它的正负就改变,所以变号
2.2.5如果行列式中两行成比例,行列式等于0
考虑下面这个行列式:
首先由定理2,把它变成:
再由定理4,交换这里相同的两行,行列式形式不变,值就不变,但它的值又应该变为相反数
相反数等于本身,所以必然是0
2.2.6把一行的某个倍数加到另外一行,值不变
把第\(i\)行的\(k\)倍加到第\(j\)行:
用到了定理5和定理3
这也是后面要常用的一个定理
2.2.7终于没了
然后又因为定理1,下面几个定理对行的操作也都可以到列上
3如何更快的求值
3.1考虑一种特殊行列式
显然它的值是\(\prod_{i=1}^{n}a_{i,i}\),因为求行列式的那个\(\sum\)里其他项都会乘个0
3.2高斯消元
这个东西还可以用来解n元一次方程
有了这个,就可以进入高斯消元了
考虑把一个普通行列式变成上面那种形式
一列一列的消
考虑第一列,对于第\(i\)行(当然\(i\)不等于1),让它每个元素\(a_{i,j}\)减去\(\dfrac{a_{i,1}}{a_{1,1}}\times a_{1,j}\)(定理6)
这样就让\(2\)到\(n\)行的第一列的元素变成了0
然后用第2行的\(a_{2,2}\)到\(a_{2,n}\)这\(n-1\)个元素,将\(a_{3,2}\)到\(a_{n,2}\)(也就是第二列)消成0
同理,用第3行消第3列,第4行消第4列,\(\dots\)
当然不用考虑在消后面的元素的时候把前面已经消成0的破坏
因为如果在用第\(i\)行消第\(i\)列,\(a_{i,1}\)到\(a_{i,i-1}\)和他们之间的每一个数肯定都已经消成了0,所以用他们的任意倍去减下面的\(a_{j,h}(1\leq h\leq i-1\),它已经被消成0了)还是0
所以可以写出代码
因为没有找到题,本文所有代码未验证,可能有错
double gauss(){
double x=1;
for(reg int i=1;i<=n;i++){
for(reg int j=i;j<=n;j++){//找一个不为0的项
if(std::abs(a[j][i])>1e-8) {
if(j==i) break;
std::swap(a[i],a[j]);
x=-x;//根据定理5,交换两行行列式变好
break;
}
}
if(std::fabs(a[i][i])<=1e-8) return 0;//没有不为0的项了,行列式值为0
for(reg int j=i+1;j<=n;j++){
double k=a[j][i]/a[i][i];
for(reg int h=1;h<=n;h++)
a[j][h]-=a[i][h]*k;
}
}
for(reg int i=1;i<=n;i++) x*=a[i][i];
return x;
}
然而,这样可能精度会出问题
比如我们被给定的行列式都由正数组成,然而我们一直在用实数来算
所以我们基本不会用上面那种。。。
3.3精度更高的高斯消元
可以发现,整个高斯消元中,产生最大精度误差的是这一句:
double k=a[j][i]/a[i][i];
考虑如何减小
比如我们由两个数,\(a=10^5,b=10^{-5}\)
则\(\dfrac{k}{10^5}\)和\(\dfrac{k}{10^{-5}}\)谁精度高呢?这里\(k\)是c++浮点数
当然是第一种,因为\(\dfrac{k}{10^{-5}}=k\times 10^5\),所以,更多的数位会去用来表示整数部分,表示小数部分的少了,就没有第一种误差小
所以我们每次可以选一个最大的a[i][i]
来做除数
具体实现在上一个代码改一改就好了
因为在数学上一个矩阵或是什么中的最大数叫主元,所以整个方法叫主元高斯消元
double gauss(){
double x=1;
for(reg int i=1;i<=n;i++){
int maxline=i;//最大数的行号
for(reg int j=i+1;j<=n;j++)
if(std::abs(a[maxline][i])<std::fabs(a[j][i])) maxline=j;
if(maxline!=i) std::swap(a[maxline],a[i]),x=-x;
for(reg int j=i+1;j<=n;j++){
double k=a[j][i]/a[i][i];
for(reg int h=1;h<=n;h++)
a[j][h]-=k*a[i][h];
}
}
for(reg int i=1;i<=n;i++) x*=a[i][i];
return x;
}
3.4辗转相消法
又是然而,主元高斯消元减少了误差,但没完全避免
所以我们能不能在过程中不用实数运算来避免误差呢?
用一种类似于辗转相除的方法
当然也要用定理6
还是一列一列消元,不断用数大的行减去数小的行,直到减成0
然后可以像求\(\gcd\)的时候一样把减改成模
然后这种方法的复杂度是\(O(n^2(n+\log n))\)
因为求\(n\)个数的\(\gcd\)的最大公因数实际上是\(O(n+\log n)\)的
直接看代码吧,我实在想不出怎么描述了.....
很像辗转相除的
但是它没有用double
的运算,所以它的常数和前面两种差别不大
int gauss(){
int x=1;
for(reg int i=1;i<=n;i++){
for(reg int j=i+1;j<=n;j++){
while(a[j][i]!=0){
int k=a[i][i]/a[j][i];
for(reg int h=1;h<=n;h++) a[i][h]-=a[j][h]*k;
for(reg int h=1;h<=n;h++) std::swap(a[i][h],a[j][h]);
x=-x;
}
}
}
for(reg int i=1;i<=n;i++) x*=a[i][i];
return x;
}
4.题目
代填
5.OI以外的简单应用
代填
打个笔记latex快累死我了.....