摘要
本文主要讲解矩阵乘法和矩阵快速幂。内容不难,都是定理,重点是矩阵乘法的应用。
蓝桥杯知识点汇总:
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}}} [12095−13−6]
-------引用自维基百科。
知道了矩阵是什么后,看看什么是矩阵乘法。
矩阵乘法
数学中,矩阵乘法(英语: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[n−1]+f[n−2]
从第三项开始,每一项都是前两项的和,我们能否用矩阵相乘来表示这个关系呢?
设矩阵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[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
[
n
−
1
]
+
f
[
n
−
2
]
f[n] = f[n-1] + f[n-2]
f[n]=f[n−1]+f[n−2] 转化为:
对于斐波那契数列来说,关系矩阵是不变的,所以如果要用上式来求斐波那契数列的第
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+1时刻,A等于t时刻的A+B,B等于t时刻的A。
我们设矩阵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]∗关系矩阵的(n−1)次方
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();
}
}