初等数论------矩阵快速幂
1月份的时候讲过矩阵快速幂 但是早就忘得差不多了 捡一下()
昨天讲的矩快 板子愣是卡了我半天 最后才发现取模取早了{捂脸}
昨天(2022.7.27)朱德远过生日诶
生快生快🎂!!!
来直入主题:
一 矩阵相关:
1、定义
矩阵 顾名思义 一个矩形的方阵
在百科中写到:
矩阵是一个按照长方阵列排列的复数或实数集合
总之,矩阵就是一堆数,按照矩形排列形成的集合
那么,我们所需要记录的也就是它的长、宽以及矩阵中存储的元素
特殊的,长宽相等的矩阵我们定义它为方阵
当两个矩阵的长宽相等时,我们认为这两个矩阵为同型矩形
2、基本运算
加法运算:
在矩阵加法中 两个矩阵可相加的前提是 两个矩阵为同型矩阵;
此时 我们只需将对应位置的元素相加即可 () 如下:
在矩阵的加法运算中,满足交换律和结合律,也就是
A + B = B + A
(A + B) + C = A + (B + C)
非同型矩阵相加也是可以实现的 但此处不加赘述
减法运算:
相类比于加法运算 两个矩阵可相减的前提也是 两个矩阵为同型矩阵;
由于减法是加法的逆运算 所以对应的 我们只需要将两个矩阵对应的元素相减即可() 如下:
数乘运算:
由于实数中不存在数乘的概念 所以我们用向量的只是来理解这个数乘
我们钦定矩阵的型不变 因此我们只将系数与元素相乘 如下():
在数乘中 满足下列定律:
(λμ)A = λ(μA)
(λ + μ)A = λA + μA
λ(A +B) = λA + λB
乘法运算:
终于来到乘法了()
矩阵的乘法运算 本质上就是矩阵乘矩阵;
但矩阵相乘有前提条件
即:
满足A矩阵的列数等于B矩阵的行数
设A矩阵为m * k,B矩阵为k * n,则A、B可以相乘 相乘后得到的矩阵C大小为 m * n;
那么怎么相乘呢(?)
非常简单
用A的第i行元素与B的第j列对应元素依次相乘并相加 就得到了C矩阵中的第i,j位的元素
这么说是不是很懵(?)
我们来看个图就清晰了:
就是像这样();
在矩阵乘法中 满足以下运算律:
(AB)C = a(BC)
(A + B)C = AC + BC
C(A + B) = CA + CB
然而 在乘法中 我们都知道 1乘以任何数都等于这个数
在矩阵乘法中 矩阵也有自己的1 但它并不是数字1 而是:
单位矩阵
单位矩阵的特点 :在一个大小为m * n的矩阵中 单位矩阵的大小为n * n,我们称之为n阶方阵 即一个正方形矩阵;
它的内部元素中 从左上至右下均为1 剩下元素均为0;
二、矩阵快速幂
OK矩阵的基本运算就这样讲完了 (完结撒花)
接下来进入正题:矩阵快速幂
矩阵快速幂其实就是在快速幂中将矩阵相乘即可
1月份的时候我写的是函数 lyn学长给我们讲了重载运算符 看上去更加清晰简便 那我们就来讲一下重载运算符:
首先先开一个结构体 用来存储矩阵
在结构体中 我们需要两个函数 一个用来将矩阵清零 一个用来预处理构造单位矩阵:
1 struct M{ 2 int a[101][101]; 3 inline void clean(){ 4 for(register int i(1);i <= n;i ++){ 5 for(register int j(1);j <= n;j ++){ 6 a[i][j] = 0; 7 } 8 } 9 }//也可以写成 memset(a,0,sizeof(a)); 10 inline void init(){ 11 for(register int i(1);i <= n;i ++){ 12 a[i][i] = 1; 13 } 14 } 15 }A;
接下来 我们需要重载乘号运算符:
1 inline M operator *(const M&x,const M&y){ 2 M z; 3 z.clean();//相乘之前必须先将存储矩阵清零 4 for(register int i(1);i <= n;i ++){ 5 for(register int j(1);j <= n;j ++){ 6 for(register int k(1);k <= n;k ++){ 7 z.a[i][j] += (x.a[i][k] * y.a[k][j]) % mod; 8 z.a[i][j] %= mod;//记得取模!!!!!!(我就卡在这了) 9 } 10 } 11 } 12 return z; 13 }
接下来就是传统的快速幂算法:
1 inline M qpow(M a,int b){ 2 M res; 3 res.init();//先预处理构造单位矩阵 4 while(b){ 5 if(b & 1){ 6 res = res * a; 7 } 8 a = a * a; 9 b >>= 1; 10 } 11 return res; 12 }
就这样 矩阵快速幂的函数部分就完成了!!!!!
带来AC大代码(题目链接):
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cstring> 5 #include <climits> 6 #include <cmath> 7 #include <cassert> 8 #include <set> 9 #include <map> 10 #include <stack> 11 #include <queue> 12 #define int long long 13 using namespace std; 14 inline int read(){ 15 int x = 0,f = 1; 16 char ch = getchar(); 17 while(ch < '0' || ch > '9'){ 18 if (ch == '-') f = -1; 19 ch = getchar(); 20 } 21 while(ch >= '0' && ch <= '9'){ 22 x = x * 10 + ch - '0'; 23 ch = getchar(); 24 } 25 return x * f; 26 } 27 const int mod = 1e9 + 7; 28 int n; 29 int k; 30 struct M{ 31 int a[101][101]; 32 inline void clean(){ 33 for(register int i(1);i <= n;i ++){ 34 for(register int j(1);j <= n;j ++){ 35 a[i][j] = 0; 36 } 37 } 38 } 39 inline void init(){ 40 for(register int i(1);i <= n;i ++){ 41 a[i][i] = 1; 42 } 43 } 44 }A; 45 inline M operator *(const M&x,const M&y){ 46 M z; 47 z.clean(); 48 for(register int i(1);i <= n;i ++){ 49 for(register int j(1);j <= n;j ++){ 50 for(register int k(1);k <= n;k ++){ 51 z.a[i][j] += (x.a[i][k] * y.a[k][j]) % mod; 52 z.a[i][j] %= mod; 53 } 54 } 55 } 56 return z; 57 } 58 inline M qpow(M a,int b){ 59 M res; 60 res.init(); 61 while(b){ 62 if(b & 1){ 63 res = res * a; 64 } 65 a = a * a; 66 b >>= 1; 67 } 68 return res; 69 } 70 signed main(){ 71 M p; 72 M q; 73 p.init(); 74 n = read(); 75 k = read(); 76 for(register int i(1);i <= n;i ++){ 77 for(register int j(1);j <= n;j ++){ 78 A.a[i][j] = read(); 79 } 80 } 81 p = qpow(A,k); 82 for(register int i(1);i <= n;i ++){ 83 for(register int j(1);j <= n;j ++){ 84 cout << p.a[i][j] << " "; 85 } 86 cout << endl; 87 } 88 return 0; 89 }
带来一道板子题(题目链接)
题目描述
大家都知道,斐波那契数列是满足如下性质的一个数列:
Fn=
{
1 (n≤2)
Fn−1+Fn−2 (n≥3)
}
请你求出 Fn mod 109+7F_n \bmod 10^9 + 7的值。
输入格式
一行一个正整数 nn
输出格式
输出一行一个整数表示答案。
输入输出样例
5
5
10
55
说明/提示
【数据范围】
对于 60%60\% 的数据,1≤n≤92;
对于 100%100\% 的数据,1≤n<2631\le n < 2^{63}。
这个问题就非常简单 不过对于初学矩阵的同学 构造这个矩阵还是有点难度的:
首先 由于这个该死的数据范围 我们无法使用普通的递推去完成这个问题
于是我们可以使用转移矩阵来加速:
我们知道 在斐波那契数列中 f[i] = f[i - 1] + f[i - 2];
那也就是说
我们可以用这个关系去推导转移矩阵、
设f相关矩阵为
|f[i] |
|f[i - 1 |
我们想用一个转移矩阵 去建立f[i] f[i - 1] 和 f[i - 1] f[i - 2] 之间的关系
于是 我们设出一个转移矩阵
|a b|
|c d|
当我们把转移矩阵和递推阵相乘的时候
我们就会得到
|a b| \ / |f[i - 1]| ----- |af[i - 1] + bf[i]| ----- |f[i] |
|c d| / \ |f[i - 2]| ----- |cf[i - 1] + df[i]| ----- |f[i - 1]|
(实在没有办法了 只能用这种符号表示一下乘法 大家感性理解吧())
此时 我们就可以得到 为了使左右等式成立 我们可以
令c = 1,d = 0得到f[i - 1]
令a = 1,b = 1得到f[i](根据斐波那契公式f[i] = f[i - 1] + f[i - 2])
这样一来 我们就得到了以矩阵为单位的转移式
由此而推 我们可以一直把i - 1 i - 2推广到1 和 2 那也就是说 最后一个转移点在f[1] 和 f[2]上
那么式子就变成了
|1 1| n\ / |f[1]| ----- |f[i] |
|1 0| / \ |f[2]| ----- |f[i - 1]|
那么这个n应该等于多少呢
从最大的i 也就是n开始 到2 总共经历了n - 2次递推
所以 转移矩阵自我相乘了n - 2次
也就是n - 2次方
由此 该问题就此解决
我们用矩阵快速幂就可以解决这个问题
来来来 上代码:
1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 #include <cstring> 5 #include <climits> 6 #include <cmath> 7 #include <cassert> 8 #include <set> 9 #include <map> 10 #include <stack> 11 #include <queue> 12 #define int long long 13 using namespace std; 14 inline int read(){ 15 int x = 0,f = 1; 16 char ch = getchar(); 17 while(ch < '0' || ch > '9'){ 18 if (ch == '-') f = -1; 19 ch = getchar(); 20 } 21 while(ch >= '0' && ch <= '9'){ 22 x = x * 10 + ch - '0'; 23 ch = getchar(); 24 } 25 return x * f; 26 } 27 const int mod = 1e9 + 7; 28 int n; 29 struct M{ 30 int a[101][101]; 31 inline void clean(){ 32 memset(a,0,sizeof(a)); 33 } 34 inline void init(){ 35 for(register int i(1);i <= 101;i ++){ 36 a[i][i] = 1; 37 } 38 } 39 }A; 40 inline M operator *(const M&x,const M&y){ 41 M z; 42 z.clean(); 43 for(register int i(1);i <= 10;i ++){ 44 for(register int j(1);j <= 10;j ++){ 45 for(register int k(1);k <= 10;k ++){ 46 z.a[i][j] += (x.a[i][k] * y.a[k][j]) % mod; 47 z.a[i][j] %= mod; 48 } 49 } 50 } 51 return z; 52 } 53 inline M qpow(M a,int b){ 54 M res; 55 res.init(); 56 while(b){ 57 if(b & 1){ 58 res = res * a; 59 } 60 a = a * a; 61 b >>= 1; 62 } 63 return res; 64 } 65 signed main(){ 66 M p; 67 M q; 68 p.init(); 69 scanf("%lld",&n); 70 if(n <= 2) return cout << 1 << endl,0; 71 A.a[1][1] = 1; 72 A.a[1][2] = 1; 73 A.a[2][1] = 1; 74 A.a[2][2] = 0;//构造转移矩阵 75 p = qpow(A,n - 2); 76 M g; 77 g.a[1][1] = 1; 78 g.a[2][1] = 1;//斐波那契初始f[1]f[2] 79 q = p * g; 80 printf("%lld\n",q.a[1][1] % mod); 81 return 0; 82 }
完结撒花!!
数论难死。。。。。。。。。。