updata on 2020.3.29
修了一些炸了的公式,原本在洛谷博客上是好的,搬過來成這樣了
然后去掉了定理部分的列表,把一堆行間公式放在列表里是怎么想的。。。
然而#4和#5還是沒填
on 2020.11.21
這篇后面兩個內容耽誤太久了,現在並不准備把它寫完了,置頂也去掉了
發現那個奇怪的高斯消元方法好像是假的,刪掉了它
然而這東西在OI里好像並沒有什么用
這上面的題不多,洛谷里連標簽都沒有
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快累死我了.....