矩陣乘法和矩陣快速冪

摘要

本文主要講解矩陣乘法和矩陣快速冪。內容不難,都是定理,重點是矩陣乘法的應用。

藍橋杯知識點彙總:
https://blog.csdn.net/GD_ONE/article/details/104061907

矩陣

數學上,一個m×n{\displaystyle m\times n}的矩陣是一個由mm行(row)n列(column)元素排列成的矩形陣列。矩陣裏的元素可以是數字、符號或數學式。以下是一個由6個數字元素構成的2行3列的矩陣:

[19132056]{\displaystyle {\begin{bmatrix}1&9&-13\\20&5&-6\end{bmatrix}}}
-------引用自維基百科。

知道了矩陣是什麼後,看看什麼是矩陣乘法。

矩陣乘法

數學中,矩陣乘法(英語:matrix multiplication)是一種根據兩個矩陣得到第三個矩陣的二元運算,第三個矩陣即前兩者的乘積,稱爲矩陣積(英語:matrix product)。設A{\displaystyle A}n×m{\displaystyle n\times m}的矩陣,B{\displaystyle B}m×p{\displaystyle m\times p}的矩陣,則它們的矩陣積AB{\displaystyle AB}n×p{\displaystyle n\times p}的矩陣。A{\displaystyle A}中每一行的m{\displaystyle m}個元素都與B{\displaystyle B}中對應列的m{\displaystyle m}個元素對應相乘,這些乘積的和就是AB{\displaystyle AB}中的一個元素。
-------引用自維基百科。

直接看圖
在這裏插入圖片描述
矩陣乘法的代碼實現就是直接模擬乘法過程,所以沒什麼好說的,直接給出代碼:

public static void mut_mul(long[][] a, long[][] b, long[][] c){ // c = a * b
		long[][] t = {{0, 0}, {0, 0}}; // 中間數組
		
		for(int i = 0; i < 2; i++)
			for(int j = 0; j < 2; j++)
				for(int k = 0; k < 2; k++)
					t[i][j] = (t[i][j] + (a[i][k]*b[k][j]) % mod)%mod;
					
		for(int i = 0; i < 2; i++) c[i] = Arrays.copyOf(t[i], 2);
	}

矩陣快速冪

矩陣快速冪和普通快速冪思路完全一樣,實現代碼的時候將整數乘法變爲矩陣乘法就可以了。
代碼:

public static void mut_mul(long[][] a, long[][] b, long[][] c){
		long[][] t = {{0, 0}, {0, 0}}; 
		
		for(int i = 0; i < 2; i++)
			for(int j = 0; j < 2; j++)
				for(int k = 0; k < 2; k++)
					t[i][j] = (t[i][j] + (a[i][k]*b[k][j]) % mod)%mod;
					
		for(int i = 0; i < 2; i++) c[i] = Arrays.copyOf(t[i], 2);
	}
	
	
	public static void qmi(long[][] a, long b, long[][] c){// a = a * c^b
		while(b != 0){
			if((b & 1) == 1){
				mut_mul(a, c, a);
			}
			b >>= 1;
			mut_mul(c, c, c);
		}
	}

以上就是矩陣快速冪的代碼了。

矩陣乘法的應用

矩陣是數學家求解線性方程組的過程中發明的,對於:

在這裏插入圖片描述
將未知數的係數歸爲一個矩陣,將未知數歸爲一個矩陣,將結果歸爲一個矩陣得到:
在這裏插入圖片描述
用第一個矩陣乘以第二個矩陣,就可以得到原來的線性方程組。
所以矩陣相乘更像是一種函數,讓第一個矩陣通過某種映射關係,轉化到另一個矩陣。

對於斐波那契數列數列:1,1,2,3,5,7...1,1,2,3,5,7...
轉化爲關係式:f[n]=f[n1]+f[n2]f[n] = f[n-1] + f[n-2]
從第三項開始,每一項都是前兩項的和,我們能否用矩陣相乘來表示這個關係呢?
設矩陣A爲:
在這裏插入圖片描述
設矩陣C爲:
在這裏插入圖片描述
矩陣A怎麼變爲矩陣C呢?
矩陣C的第一個元素爲f[n1],f[n2]Af[n1]Af[n1]1f[n2]0f[n1]f[n1]1+f[n2]1f[n]f[n-1],第二個元素爲f[n-2] 。矩陣A的第一個元素也爲f[n-1],那麼讓矩陣A中的f[n-1]乘以1,讓f[n-2]乘以0,不就得到f[n-1]了嘛,然後讓f[n-1]*1 + f[n-2]*1, 不就得到f[n]了嘛

所以我們構造出一個關係矩陣:
在這裏插入圖片描述
於是可以將f[n]=f[n1]+f[n2]f[n] = f[n-1] + f[n-2] 轉化爲:
在這裏插入圖片描述對於斐波那契數列來說,關係矩陣是不變的,所以如果要用上式來求斐波那契數列的第nn的話,我們可以這樣做:
在這裏插入圖片描述
即用矩陣[1,1][1, 1]乘以關係矩陣n-1次。
這樣做的時間複雜度反而比用原本的關係式遞推求f[n]f[n]更高了,但是我們可以對其進行優化,那就是用矩陣快速冪來算關係矩陣的(n-1)次方。這樣時間複雜度就降到了O(log(n))O(log(n))級別。

一般要用矩陣乘法求解的問題都要構造一個關係矩陣,能否正確的構造出關係矩陣,就是解題的關鍵了。


例題:

藍橋杯 算法訓練 奇異的蟲羣

問題描述
  在一個奇怪的星球上駐紮着兩個蟲羣A和B,它們用奇怪的方式繁殖着,在t+1時刻A蟲羣的數量等於t時刻A蟲羣和B蟲羣數量之和,t+1時刻B蟲羣的數量等於t時刻A蟲羣的數量。由於星際空間的時間維度很廣闊,所以t可能很大。OverMind 想知道在t時刻A蟲羣的數量對 p = 1,000,000,007.取餘數的結果。當t=1時 A種羣和B種羣的數量均爲1。
輸入格式
  測試數據包含一個整數t,代表繁殖的時間。
輸出格式
  輸出一行,包含一個整數,表示對p取餘數的結果
樣例輸入
  10
樣例輸出
  89
樣例輸入
  65536
樣例輸出
  462302286
 
數據規模和約定
  對於50%的數據 t<=10^9
  對於70%的數據 t<=10^15
  對於100%的數據 t<=10^18


題解:
正解就是構造一個關係矩陣,然後用求斐波那契數列第n項的方式求解答案就可以了,先不看下面的題解,自己嘗試構造一下吧。

題目說:在t+1AtA+BBtAt+1時刻,A等於t時刻的A+B,B等於t時刻的A
我們設矩陣A爲[At,Bt][A_t, B_t], 矩陣C爲[At+Bt,At][A_t + B_t, A_t]
則關係矩陣爲:
在這裏插入圖片描述

得到關係矩陣後就直接套用矩陣乘法和矩陣快速冪模板就可以了,t[1,1](n1)第t時刻的矩陣等於[1, 1] * 關係矩陣的(n-1)次方

AC代碼:

因爲最大數據是101810^{18},注意用long,還有一點是,因爲數據太大了,矩陣乘法中,加法加完也要取模。

import java.io.*;
import java.util.*;

public class Main {
	
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
	
	static final int mod = 1000000007;
	
	public static void mut_mul(long[][] a, long[][] b, long[][] c){
		long[][] t = {{0, 0}, {0, 0}}; // 中間數組
		
		for(int i = 0; i < 2; i++)
			for(int j = 0; j < 2; j++)
				for(int k = 0; k < 2; k++)
					t[i][j] = (t[i][j] + (a[i][k]*b[k][j]) % mod)%mod;
					
		for(int i = 0; i < 2; i++) c[i] = Arrays.copyOf(t[i], 2);
	}
	
	
	public static void qmi(long[][] a, long b, long[][] c){// a = a * c^b
		while(b != 0){
			if((b & 1) == 1){
				mut_mul(a, c, a);
			}
			b >>= 1;
			mut_mul(c, c, c);
		}
	}
	
	public static void main(String[] args) throws NumberFormatException, IOException{
		long n;
		n = Long.valueOf(in.readLine());
		long[][] a = {{1,1}, {0,0}}; //初始A和B都是1, 然後因爲矩陣乘法都是二維數組,所以這裏也將其寫作二維數組。
		long[][] c = {{1, 1}, {1, 0}};// 關係數組
		qmi(a, n-1, c);
		out.write(a[0][0] + "\n");
		out.flush();
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章