矩阵乘法和矩阵快速幂

摘要

本文主要讲解矩阵乘法和矩阵快速幂。内容不难,都是定理,重点是矩阵乘法的应用。

蓝桥杯知识点汇总:
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();
	}
}

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