矩阵乘法和矩阵快速幂

摘要

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

蓝桥杯知识点汇总:
https://blog.csdn.net/GD_ONE/article/details/104061907

矩阵

数学上,一个 m × n {\displaystyle m\times n} m×n的矩阵是一个由 m m m行(row)n列(column)元素排列成的矩形阵列。矩阵里的元素可以是数字、符号或数学式。以下是一个由6个数字元素构成的2行3列的矩阵:

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

知道了矩阵是什么后,看看什么是矩阵乘法。

矩阵乘法

数学中,矩阵乘法(英语:matrix multiplication)是一种根据两个矩阵得到第三个矩阵的二元运算,第三个矩阵即前两者的乘积,称为矩阵积(英语:matrix product)。设 A {\displaystyle A} A n × m {\displaystyle n\times m} n×m的矩阵, B {\displaystyle B} B m × p {\displaystyle m\times p} m×p的矩阵,则它们的矩阵积 A B {\displaystyle AB} AB n × p {\displaystyle n\times p} n×p的矩阵。 A {\displaystyle A} A中每一行的 m {\displaystyle m} m个元素都与 B {\displaystyle B} B中对应列的 m {\displaystyle m} m个元素对应相乘,这些乘积的和就是 A B {\displaystyle AB} 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... 1,1,2,3,5,7...
转化为关系式: f [ n ] = f [ n − 1 ] + f [ n − 2 ] f[n] = f[n-1] + f[n-2] f[n]=f[n1]+f[n2]
从第三项开始,每一项都是前两项的和,我们能否用矩阵相乘来表示这个关系呢?
设矩阵A为:
在这里插入图片描述
设矩阵C为:
在这里插入图片描述
矩阵A怎么变为矩阵C呢?
矩阵C的第一个元素为 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-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[n1],f[n2]Af[n1]Af[n1]1f[n2]0f[n1]f[n1]1+f[n2]1f[n]

所以我们构造出一个关系矩阵:
在这里插入图片描述
于是可以将 f [ n ] = f [ n − 1 ] + f [ n − 2 ] f[n] = f[n-1] + f[n-2] f[n]=f[n1]+f[n2] 转化为:
在这里插入图片描述对于斐波那契数列来说,关系矩阵是不变的,所以如果要用上式来求斐波那契数列的第 n n n的话,我们可以这样做:
在这里插入图片描述
即用矩阵 [ 1 , 1 ] [1, 1] [1,1]乘以关系矩阵n-1次。
这样做的时间复杂度反而比用原本的关系式递推求 f [ n ] f[n] f[n]更高了,但是我们可以对其进行优化,那就是用矩阵快速幂来算关系矩阵的(n-1)次方。这样时间复杂度就降到了 O ( l o g ( n ) ) 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 + 1 时 刻 , A 等 于 t 时 刻 的 A + B , B 等 于 t 时 刻 的 A t+1时刻,A等于t时刻的A+B,B等于t时刻的A t+1AtA+BBtA
我们设矩阵A为 [ A t , B t ] [A_t, B_t] [At,Bt], 矩阵C为 [ A t + B t , A t ] [A_t + B_t, A_t] [At+Bt,At]
则关系矩阵为:
在这里插入图片描述

得到关系矩阵后就直接套用矩阵乘法和矩阵快速幂模板就可以了, 第 t 时 刻 的 矩 阵 等 于 [ 1 , 1 ] ∗ 关 系 矩 阵 的 ( n − 1 ) 次 方 第t时刻的矩阵等于[1, 1] * 关系矩阵的(n-1)次方 t[1,1](n1)

AC代码:

因为最大数据是 1 0 18 10^{18} 1018,注意用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();
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值