線性代數(不完全)與矩陣合集


雜談

一.前言

​ 我,不喜歡線性代數。這是前前后后斷斷續續寫的,語言和邏輯不清見諒。(下次還敢

二.線性代數與矩陣

​ 大概是矩陣吧,一個關於線性的詭異東西。線性代數拆開,就變成了代數和線性。代數很好理解,無非就是解一些方程,或者帶一些數進去,而關於所有的元都要求次數為1,即為線性。

1.矩陣的定義

​ 在二維上一堆數排在一起。吧。就是矩形其實,只不過由一些數拼湊而成。很神秘,大小為 \(n*m\) 的矩陣有 n 行 m 列。按照我的理解,矩陣中的數字在運用中多半是代表着系數。

2.矩陣的加減法

​ 前提是兩個矩陣大小相等,假設結果矩陣為 \(res\) ,有 \(res_{ij}=A_{ij}+B_{ij}\) ,減法同理,還算科學。

3.矩陣的乘法

​ 前提不是大小相等,而是……\(A\) 的大小為 \(n*m\), B 的大小為 \(m*p\) ,\(res\) 的大小為 \(n*p\) 有點像把中間的 \(m\) 給消掉了。(祖瑪嗎這)乘法其實是我覺得特別魔幻的一個部分,第一次看到就會覺得頭大的那種。下面給出柿子。

\[Res_{i,j}=\sum\limits_{k=1}^{m} A_{i,k}*B_{k,j} \]

(其中 i 范圍為 n ,j 為 p,這也能解釋res的大小,同時意識到 k 在兩個矩陣中都有,范圍自然是 m)

​ 多提一嘴這個其實是計算幾何中點積的形式,在 m 維中,雖然我不知到有什么特別的意義,但是也許可以加強記憶?

​ 順便這個和 \(Floyd\) 的柿子長得很像,於是也有一類最短路題是關於這個的。

4.系數矩陣

​ 假如我們有一些線性方程,他們共同組成了一個方程組。例如:

\[\begin{align} 2a+3b&=6\\ 7b+c&=8\\ 3a+4c&=11 \end{align} \]

可能會有解,也可能會沒有吧。亂寫的。

​ 但是這個方程組可以寫成一個很奇妙的形式。

\[2~~~~3~~~~0~~~~~~~~~~~~~~a~~~~~~~~~6\\ 0~~~~7~~~~1~~~~~~~~~~~~~~b~~~~~~~~~8\\ 3~~~~0~~~~4~~~~~~~~~~~~~~c~~~~~~~11\\ \]

實際上左邊的矩陣乘上中間矩陣等於右邊矩陣所代表的形式就是上面的方程組(可以手玩),換而言之一個可行的中間矩陣就可以當作一組解。

​ 理論上講,一般 n 個元需要 n 個線性方程才解的出來,這 n 個方程用上述方法會整出一個 \(n*n\) 的矩陣,它叫做系數矩陣。

5.增廣矩陣

​ 就是將方程右邊的數值和系數矩陣拼在一起。叫做增廣矩陣。還是上面的例子。

\[2~~~~3~~~~0~~~~~~~~~6\\ 0~~~~7~~~~1~~~~~~~~~8\\ 3~~~~0~~~~4~~~~~~~~11\\ \]

6.初等行變換

​ 顧名思義,是關於行的操作。一共有三種操作。下文的 k 不能等於 0,但是可以取負。

  • 將兩行交換
  • 一行加上另一行的 k 倍
  • 一行乘上 k

初等行變換會多次運用於之后的講解中。按照我的理解,初等行變換並不會改變這個矩陣,按照百度的說法,若是兩個矩陣可以通過行變換相互轉化,那么稱它們是等價的。在做題中一般可以使求解簡化,且變化時答案和矩陣所對應的值與關系不會有太大改變。

7.左乘一個矩陣

​ 其實我是想要說對一個矩陣實行初等行變換等於左乘一個矩陣。(左乘指將這個矩陣放在左邊做乘法,因為矩陣的乘法不滿足交換律所以有左右之分)。

​ 換而言之,所有的初等行變換可以通過左乘一個矩陣來實現相同的效果。

8.高斯消元

​ 一個基於增廣矩陣和初等行變換的未知數求解算法,有時間就單開一篇。

9.矩陣加速

​ 常見於各種DP題中,比如最經典的斐波拉契數列,它的遞推式滿足

\[F_i=F_{i-1}+F_{i-2} \]

於是可以轉化為

\[({F_{i-1}}~~~~{F_{i-2}})*(_{1 ~0}^{1~1})=(F_{i}~~~~F_{i-1})\\ \]

可以細細感悟其中個過程,一般只要遞推式是單純的線性幾項加減,都可以轉化為矩陣形態,可以發現,一般乘上的是一個只有常數的矩陣,如果不需要中間的值,就可以把很長的柿子寫出來,可以矩陣滿足乘法結合律,拿矩陣做一個快速冪蕪湖起飛~

10.特殊的矩陣

​ 對角線矩陣:只有主對角線有值的矩陣

​ 上三角矩陣:只有上半部分有值(包括主對角線)的矩陣。

三.矩陣的逆

​ 關於這個問題,涉及到一小段數學史。依我之拙見,數學一開始只有在整數域中的加法。需要反向求解加法的時候就依附於加法創造了減法(加上一個數的相反數)。而乘法也是由於加法的次數過多而產生的。

​ 但是思考到乘法的逆運算除法的時候,也是添加了一個定義,用小學的話來說叫做“除以一個數等於乘上這個數的倒數”。而這個“倒數”( \(\frac{1}{x}\) ),就是整數域中的逆。

​ 而后數學的范圍在不斷擴大,有小數,實數,復數,有理數,無理數等,他們都是不同的數域,縱觀之下,會發現其中的逆與一個叫作“元”的東西有着密不可分的聯系。

​ 此處批注:一般都只會討論方陣的逆

​ 首先思考元的定義。在實數域中,元是“1”,也就是常說的“單位1”。但是元之所以為元是為什么呢?我曾記得有一個表情包是這樣的:

\(1*0=0\) 的原因到底是 1 乘上任何數都是那個數,還是 0 乘任何數都是 0 呢?

雖然比較扯淡,但是這句話也確實點出了一些元的性質:對於任何一個數域中的數 \(x\),都有元 \(e\) 使得 \(x*e=x\) ,當然這里的乘號根據在這個域中的定義是不同的,意會就好。當然的,不是每一種數域都有自己的元,幸運的是,矩陣有着自己的元。

​ 矩陣的元叫做單位矩陣。通常記作 \(I\) ,它的形式是一個主對角線為 1 的方陣。其余的數為0,顯然滿足元的定義。

​ 逆,元,逆元。

​ 這三者從名字上就有種深刻的聯系,說到逆元,肯定首先會先想到取模運算中,做除法需要求逆元。而這樣做的根據就是:在除法中,也叫做求這個數的逆,除一個數,等價於乘上這個數的逆元。

逆元

​ 對於一個元素 \(A\) 若有 \(B\) 使得 \(A*B=e\) 那么稱 B 為 A 的逆元。在某些特殊的數域中,這種關系是相互的,即 A 和 B 互為逆元。在方陣中,這種關系是相互的。此處給出簡單證明。

已知 \(AB=I\) , \(BC=I\) 求證 \(A=C\)

\(ABC=(AB)C=A(BC)=A=C\)

矩陣的除法

​ 有了上面的鋪墊(此處特指 二.6 和 二.7 和 二.8)之后我們試圖求一個矩陣 A 的逆元。換而言之我們需要一個矩陣 B 使得 \(BA=I\) ,為了好講,B 在左邊。那么又可以知道大部分矩陣都可以通過初等行變換成為 I.

​ 具體的方式為像高斯約旦消元一般消成對角線矩陣,最后再乘成1就好。

​ 若是消不成對角線矩陣,證明有一列全為 0 ,就求不了逆,相當於除數為0.

​ 由於初等行變換等於左乘一個矩陣,所以這個等效矩陣就是 B.此時有\(BA=I\).但是其實 B 不太好表示,但是沒有關系,在進行行變換的時候同時對一個單位矩陣做相同的矩陣就好,最后就會求得B。下面給出代碼(有取模)

矩陣求逆

struct Matrix{
	int size;
	int num[lst][lst];
	inline void init(){size=0;memset(num,0,sizeof(num));}
	inline Matrix makeI(int x){
		Matrix ans;
		ans.init();
		ans.size=x;
		M_for(i,0,size-1)ans.num[i][i]=1;
		return ans;
	}
	inline void ins(int x){
		init();
		size=x;M_for(i,0,x-1)M_for(j,0,x-1)num[i][j]=read();
	}
	inline void pr(){
		if(size==-1)printf("No Solution");
		else 
			M_for(i,0,size-1){
				M_for(j,0,size-1)printf("%d ",num[i][j]);
				printf("\n");
			}
	}
};
Matrix QN(Matrix x){
	Matrix ans=x.makeI(x.size);
	M_for(i,0,x.size-1){
		int k=x.num[i][i],p=i;
		M_for(j,i+1,x.size-1)if(x.num[j][i]>k)k=x.num[j][i],p=j;
		if(!k){ans.size=-1;return ans;}
		swap(x.num[i],x.num[p]);
		swap(ans.num[i],ans.num[p]);
		int alf=ksm(x.num[i][i],mod-2);
		M_for(j,0,x.size-1)if(j!=i){
			int g=(1LL*x.num[j][i]*alf)%mod;
			M_for(q,0,x.size-1){
				x.num[j][q]=((1LL*x.num[j][q]-1LL*x.num[i][q]*g)%mod+mod)%mod,
				ans.num[j][q]=((1LL*ans.num[j][q]-1LL*ans.num[i][q]*g)%mod+mod)%mod; 
			}
		}
		M_for(j,0,x.size-1)x.num[i][j]=(1LL*x.num[i][j]*alf)%mod,
		ans.num[i][j]=(1LL*ans.num[i][j]*alf)%mod;
	}
	return ans;
}

四.期望與高斯消元

​ 期望DP其實一直是一個令人頭禿的問題,大部分的都是倒着推轉移方程式就能做。但是有一些問題,他們的狀態轉移不滿足DP所代表的 DAG ,也就是會互相影響。此時會發現每一個要求的期望是一個未知數,然后從其他的狀態轉移過來也都是未知數,且一般都是線性轉移求和。

​ 於是可以對於每一個狀態列方程。下面結合着例題來講。

到 t 的路程期望

暫時沒找到例題?但是可以有轉移柿子 \(Eu=\sum_j^{u\to v}p_j(E_v+len_j)\),這個柿子很感性,看到就覺的是對的,但是並沒有那么的簡單,如果每個都想當然會把很多東西都弄錯。下面給出從期望定義開始的嚴格證明。

\[\begin{align} E_u&=\sum_i^{u\to t}p_iw_i\\ &=\sum_j^{u\to v}\sum_i^{v\to t}p_ip_j(w_i+len_j)\\ &=\sum_j^{u\to v}p_j\sum_i^{v\to t}(p_iw_i+p_ilen_j)\\ &=\sum_j^{u\to v}p_j(E_v+\sum_i^{v\to t}p_ilen_j)\\ &=\sum_j^{u\to v}p_jE_v+\sum_i^{v\to t}p_i\sum_j^{u\to v}p_jlen_j\\ &=\sum_j^{u\to v}p_j(E_v+len_j) \end{align} \]

迷惑理解:因為所有走到 u 的必然會經過與它相連的邊,所以上面的證明有一步拆分,所以就從與 u 相連的點轉移,可以類比最短路?隨便口胡的。

到 t 的異或和路徑期望

XOR和路徑

​ 這道題如果想當然的和前面一樣轉移就會是錯誤的,不信從定義開始推,會在某個地方卡住。如果用一句話總結的話就是期望的異或不是異或的期望。

​ 既然直接上期望不行,那我們轉而去求概率,因為期望一般為概率的倒數。就拿這道題來說,雖然不知道最后的期望是多少,但是考慮到異或之間每一位不相互影響,於是可以求出每一位上為 1 或者為 0 的概率。最后再求和就好。

​ 具體而言,記每一個點的出度為 \(deg\) ,\(w(i,u,v)\) 表示\(u \to v\) 這條邊的二進制第 i 位的值。\(f_{k,i}\) 表示到 k 的時候第 i 位為 1 的概率(為 0 的概率可以用1減),於是有。

\[\begin{align} f_{k,i}&=\frac{1}{deg_i}(\sum_{w(k,i,j)=0}f_{k,j}+\sum_{w(k,i,j)=1}1-f_{k,j})\\ f_{k,i}deg_i&=\sum_{w(k,i,j)=0}f_{k,j}+\sum_{w(k,i,j)=1}1-f_{k,j}\\ f_{k,i}deg_i-\sum_{w(k,i,j)=0}f_{k,j}+\sum_{w(k,i,j)=1}f_{k,j}&=\sum_{w(k,i,j)=1}1 \end{align} \]

於是方程就搞好了,對於二進制每一位高斯消元最后匯總就可。給出代碼。

const int lst=105;
struct Matrix{
	int size;
	double num[lst][lst];
	inline void gs(){
		int loc=1;
		for(int i=1;i<=size;++i){
			int p=loc;
			for(int j=loc+1;j<=size;++j)if(mabs(num[j][i])>mabs(num[p][i]))p=j;
			if(check(num[p][i]))continue;
			swap(num[loc],num[p]);
			for(int j=1;j<=size;++j)if(j!=loc){
				double gate=1.0*num[j][i]/num[loc][i];
				for(int k=1;k<=size+1;++k)num[j][k]-=num[loc][k]*gate;
			}
			loc++;
		}
	}
	inline void init(){size=0;memset(num,0,sizeof(num));}
}S;
int to[10005],fr[10005],w[10005],deg[lst],MX;
double ans;
inline void solve(int x){
	S.init();
	S.size=n;
	for(int i=1;i<=n;++i)S.num[i][i]=deg[i];
	for(int i=1,u,v;i<=m;++i){
		u=fr[i],v=to[i];
		if(w[i]&x){
			S.num[u][v]++,S.num[u][n+1]++;
			if(u^v)S.num[v][u]++,S.num[v][n+1]++;
		}
		else{
			S.num[u][v]--;
			if(u^v)S.num[v][u]--;
		}
	}
}
int main(){
	n=read();m=read();
	for(int i=1;i<=m;++i){
		fr[i]=read();to[i]=read();
		w[i]=read();
		deg[to[i]]++;
		if(fr[i]^to[i])deg[fr[i]]++;
		MX=max(MX,w[i]);
	}
	for(int i=1;i<=MX;i<<=1){
		solve(i);
		memset(S.num[n],0,sizeof(S.num[n]));
		S.num[n][n]=1;
		S.gs();
		ans+=1.0*i*S.num[1][n+1]/S.num[1][1];
	}
	printf("%.3lf",ans);
	return 0;
}

某個點的期望經過次數

趕小豬

先講感性做法,求出每個點的一直不爆炸的期望經過次數乘上爆炸概率。

\[f_{u}=\sum_{(u,v)\in E}\frac{1}{deg_v}f_v(1-P) \]

這個只是一個大概式子,但是在實際問題之中,會有出發點一開始為 1 ,終點無法對其他造成貢獻等等

在代碼注釋里面細講

const int MAXN=305;
int n,m,q,p;
int fr[MAXN*MAXN],to[MAXN*MAXN],deg[MAXN];
double S[MAXN][MAXN],P;
int main(){
	n=read();m=read();p=read();q=read();
	P=1.0*p/q;
	for(int i=1;i<=m;++i)deg[(fr[i]=read())]++,deg[(to[i]=read())]++;//記錄出度
	for(int i=1;i<=m;++i)
		S[fr[i]][to[i]]+=(1.0-P)/deg[to[i]],S[to[i]][fr[i]]+=(1.0-P)/deg[fr[i]];//系數
	for(int i=1;i<=n;++i)S[i][i]=-1.0;//起點一開始會有一個多余的+1,移項當常數
	S[1][n+1]=-1.0;//移項常數變-1
	for(int i=1,loc=1;i<=n;++i){
		double k=S[loc][i];
		int p=loc;
		for(int j=loc+1;j<=n;++j)if(mabs(S[j][i])>mabs(k))k=S[j][i],p=j;//高斯約旦消元
		if(check(S[p][i]))continue;
		swap(S[loc],S[p]);
		for(int j=1;j<=n;++j)if(loc!=j){
			double g=1.0*S[j][i]/S[loc][i];
			for(int k=1;k<=n+1;++k)S[j][k]-=S[loc][k]*g;
		}
		loc++;
	}
	for(int i=1;i<=n;++i)printf("%.9lf\n",S[i][n+1]/S[i][i]*P);
	return 0;
}

但是!正確性不太顯然。此處給出另一個正確性顯然的做法。

​ 設 \(f_{i,k,j}\) 為由k走 i 步到 j 的期望經過次數。易得(A為鄰接矩陣)

\[f_{i,u,j}=\sum_{(u,j)\in E} f_{i-1,u,v}*A_{v,j}*(1-P)\\ \therefore f_i=A^i \]

然后要對所有的 i 的 \(f_{i}\) ,求和,i可以到正無窮。發現它收斂,等比數列求和,需要做一個求逆。

然后下面給出感性做法的證明(感謝黃金之路和Clorf神犇的推導)

\(E _{u}=\sum\dfrac{E _{v}}{\mathrm{deg} _{v}}\)

\(f _{k,u}\)\(k\) 條邊正好到達 \(u\) 的概率。

\(f _{k+1,v}=\sum _{u=1} ^{n}f _{k,u}\times A_{v,u}\)

\(\sum _{k=0} ^{\infty} f _{k,u}\)\(1\sim n\) 的路徑經過 \(u\) 的期望次數

\[E _{u}=\sum _{i} ^{1\to n} p_{i}w _{i}=\sum _{k=0} ^{\infty} f _{k,u}=f _{0,u}+\sum _{(v,u)} A _{u,v}\sum _{k=0} ^{\infty}f_{k,v}=f _{0,u}+\sum _{(v,u)}A _{u,v}E _{v}=f _{0,u}+\sum \dfrac{E _{v}}{\mathrm{deg} _{v}}=(u=1)+\sum _{(u,v)}\dfrac{E _{v}}{\mathrm{deg} _{v}}​ \]

\(\sum _{i,1\to ^{k} u } ^{1\to n} p_{i}=f _{k,u}\)

\(1\rightleftarrows 2\rightleftarrows 3\)

\(f _{0}=(1,0,0)\)

\(f _{1}=(0,1,0)\)

\(f _{2}=(0.5,0,0.5)\)

\(f _{3}=(0,0.5,0)\)

\(f _{4}=(0.25,0,0.25)\)

\[A=\left[\begin{matrix} 0 & 0.5 & 0\\ 1 & 0& 0\\ 0 & 0.5 & 0\\ \end{matrix}\right] \]

\(A _{i,j}\) 代表 \(j\to i\) 的概率。

Clorf 強強

某條邊的期望經過次數

HNOI2013 游走

​ 這個題就是如此,求出期望經過次數分配就好。邊的經過次數由兩邊的點經過次數所決定。

\[E_{u,v}=\frac{f_u}{deg_u}+\frac{f_v}{deg_v} \]

注意到終點不能再出。直接上代碼,內附注釋。

double S[MAXN][MAXN],ans[505],f[125005],res;
bool vis[MAXN][MAXN];
int main(){
	n=read();m=read();
	for(int i=1,x,y;i<=m;++i){
		x=read();y=read();
		vis[x][y]=1;
		if(y!=n)S[x][y]=1;//終點不出
		if(x!=n)S[y][x]=1;
		degree[x]++;degree[y]++;
	}
	for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)if(vis[i][j]){
		if(i!=n)S[j][i]/=degree[i];
		if(j!=n)S[i][j]/=degree[j];
	}
	for(int i=1;i<=n;++i)S[i][i]=-1;//同上一篇
	S[1][n+1]=-1;
	int loc=1;
	for(int i=1;i<=n;++i){
		double k=S[loc][i];
		int p=loc;
		for(int j=loc+1;j<=n;++j)if(S[j][i]>k)k=S[j][i],p=j;
		if(check(k)){continue;}
		swap(S[loc],S[p]);
		for(int j=1;j<=n;++j)if(i!=j){
			double g=1.0*S[j][i]/S[loc][i];
			for(int k=1;k<=n+1;++k)S[j][k]-=g*S[loc][k];
		}
		loc++;
	}
	for(int i=1;i<=n;++i)ans[i]=S[i][n+1]/S[i][i];
	ans[n]=0;//終點不出
	for(int i=1;i<=n;++i)for(int j=1;j<=n;++j){
		if(vis[i][j])
			f[++tot]=1.0*ans[i]/degree[i]+1.0*ans[j]/degree[j];//邊的經過次數
	}
	sort(f+1,f+tot+1);
	for(int i=1;i<=tot;++i)res+=f[i]*(tot-i+1);
	printf("%.3lf",res);
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM