欢迎来到方块世界。
definition
我们可以简单的认为其为一个二维数组,或者说就是将一个矩阵分成一个个小块,其中一个个的块内有数值而已。我们通常用大写字母和其行列来表示当前的矩阵,大概是 (A _{n imes m}) 这个鸭子的。 其意思就是 (n) 行 (m) 列的一个矩阵 (A)
给出定义矩阵的Code , 这里选用结构体
struct Matrix {
int n , m ;
int a[kmaxn][kmaxn] ;
Matrix()
{
n = m = 0 ;
memset(a , 0 , sizeof(a)) ;
}
};
Base-operational rule
运算律的英文真的是绝了。日。
加减法的运算是一样的。这里只用加法来表示 。
由于这玩意放在博客园实在是放不开了,就直接拆开了.
减法同样,无非是将上文的 + 换成了 - 而已。
几种分类。
单位矩阵:
对角线上的点全部为 (1)
零矩阵 :
矩阵所有的元素全部为 (0)
对称阵
(……) 其他的好像用不到。
Mul_operational rule
首先说明矩阵乘法和向量一样,支持数乘矩阵,和矩阵乘矩阵。
数乘矩阵
数乘矩阵的话就是这样的(这里不给出矩阵了,(yy) 一下,真的好麻烦的)
矩阵乘矩阵
首先我们点明一些东西 :
- 矩阵不满足交换律,即为 : (A imes B eq B imes A)
- 矩阵不满足结合律,即为 :(A imes(B imes C) eq (A imes B) imes C)
- 矩阵满足分配律,,即为: ((A+B) imes C = AC+BC)
左分配律 :(A imes (B+C) = AB+AC) ,右分配律:((A+B) imes C = AC+BC)
因为必须满足整个式子的顺序,所以分成了左右两个分配律。
(M_1 imes M_2) 首先点明这里的顺序是不可换的。我们需要满足的是 :(,M_1) 的行要等于 (m_2) 的列,我们在下面的计算式子可以体会到。
我们继续给出一个计算式
我们给定一个具体的矩阵 (1 imes 2) (因为后面有提到斐波那契数列)
这里给出矩阵乘法的代码:
Matrix operator * (const Matrix &m1 , const Matrix &m2) {
Matrix m3 ; m3.n = m1.n , m3.m = m2.m ;
for(qwq int i = 1 ; i <= m3.n ; i++)
for(qwq int k = 1 ; k <= m1.m ; k++)
for(qwq int j = 1 ; j <= m3.m ; j++)
m3.a[i][j] = m3.a[i][j] + m1.a[i][k] * m2.a[j][k] ;
return m3 ;
}
枚举顺序与时间的关系 :
这个枚举顺序指的是上边代码的 (i,j,k) 的枚举 。
(我忘记了是不是缓存了,差不多这个意思)
在计算机中进行访问空间的时候,会首先访问缓存条,缓存是计算机对你进行的一步操作的下一步操作的预判,也就是你访问了节点 (i) ,计算机可能给你预判一下,你可能需要 (i+1) 这个节点,访问缓存往往是要更快一些的。而计算机的缓存只能取到你现在进行的相邻的数,具体什么意思,也就是计算机只能够预判一下你的这一步的周围,看一下你是否会到达 (i+1, i-1) 之类的 , 那么也就是很显然了,我们只要尽可能的访问连续的节点就可以保证时间的最优性了, 也就是上方的 (k) 的枚举必须放在最后面 (也就是枚举两个矩阵进行相乘的元素枚举)
矩阵快速幂
我们有了矩阵乘法,其他的和普通的快速幂是一样的
Matrix quick(Matrix a , int b) {
Matrix ret ;
ret.m = ret.n = 2 ;
ret.a[1][1] = 1 ; ret.a[2][2] = 1 ;
while(b)
{
if(b & 1) ret = ret * a ;
a = a * a ;
b >>= 1 ;
}
return ret ;
}
差不多就这样就行了。
应用
矩阵加速递推
满足矩阵加速递推的条件为 :
- (1.) 递推必然是从 (i-1) 的状态推导到 (i)
- (2.) 其中递推的矩阵与 (i) 是没有什么关系的。
1.求解Fibonacci
基础入门等级都够不到的递推: (f_i = f_{i - 1} + f_{i - 2})
求解 (f_n , n leq 10^9)
【solution】 :
很显然我们是有 (O(n)) 的解法的,但是看到 (n) 的这个范围,显然是不大可行的。所以我们选择优化一个。 我们是很显然的想到如果我们用 (f_i) 推导到 (f_{i+1}) 的话,我们需要 (f_{i - 1}) 这两个的,所以我们就聪明一下,我们经过某种变换从而达到 (f_{i+1}) ,这里选择用矩阵加速递推求解,我们就设递推矩阵为 (M) , 我们发现我们的 (f_{i - 1} , f_{i}) 是一个 (1 imes 2) ,最后我们得到的 (f_i , f_{i+1}) 也是一个 (1 imes 2) 的一个矩阵,从而我们就知道 (M) 这个矩阵为 (2 imes 2) 的,我们设这个矩阵为
然后我们根据矩阵乘法就能够得到如下的一个式子。
所以我们最后就得到了
然后我们就那么递推就行了;
Code
/*
By : Zmonarch
知识点:
*/
#include <bits/stdc++.h>
#define int unsigned long long
#define qwq register
#define inf 2147483647
using namespace std ;
const int kmaxn = 1e6 + 10 ;
inline int read() {
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) {if(ch == '-') f = - 1 ; ch = getchar() ;}
while( isdigit(ch)) {x = x * 10 + ch - '0' ; ch = getchar() ;}
return x * f ;
}
struct Matrix {
int n , m ;
int a[4][4] ;
Matrix()
{
n = m = 0 ;
memset(a , 0 , sizeof(a)) ;
}
};
Matrix operator * (const Matrix &m1 , const Matrix &m2) {
Matrix m3 ; m3.n = m1.n , m3.m = m2.m ;
for(qwq int i = 1 ; i <= m3.n ; i++)
for(qwq int k = 1 ; k <= m1.m ; k++)
for(qwq int j = 1 ; j <= m3.m ; j++)
m3.a[i][j] = m3.a[i][j] + m1.a[i][k] * m2.a[k][j] ;
return m3 ;
}
Matrix quick(Matrix a , int b) {
Matrix ret ;
ret.m = ret.n = 2 ;
ret.a[1][1] = 1 ; ret.a[2][2] = 1 ;
while(b)
{
if(b & 1) ret = ret * a ;
a = a * a ;
b >>= 1 ;
}
return ret ;
}
signed main() {
int n = read() ;
Matrix k1 , k2 ;
k1.m = 2 , k1.n = 1 ; k1.a[1][1] = 1 , k1.a[1][2] = 1 ;
k2.m = k2.n = 2 ;
k2.a[1][1] = 0 ; k2.a[1][2] = 1 ;
k2.a[2][1] = 1 ; k2.a[2][2] = 1 ;
k1 = k1 * quick(k2 , n - 1) ;
//printf("%lld
" , k1.a[1][1]) ;
std::cout << k1.a[1][1] ;
}
矩阵加维
(1.) 递推式中带有常数项 (k)
我们需要单独为常数项 (k) 给他开一维,不能只取递推式中带有未知数的值而到了最后加上 (k)
举个例子 , 递推式为 $f_{n} = f_{n - 1} + f_{n - 2} + k $
那么我们就可以得到初始矩阵为 $$egin{bmatrix} f_{n - 2} & f_{n - 1} & k end{bmatrix} imes A = egin{bmatrix} f_{n - 1} & f_{n} & k end{bmatrix}$$
那么这时候我们只需要求出 (A) 矩阵即可。 显然矩阵是三维的,不想推了,反正我是推导出来了。
(2.) 递推式中带有未知数
同样的,再开一维
举个例子
(f_{n} = f_{n-1} + f_{n-2} + n)
首先将未知项的递推式推导出来 $(n) = (n - 1) + 1 $(我是没推出来,别问,问就是傻逼) , 得到初始矩阵
(egin{bmatrix} f_{n } & f_{n - 1} & n & 1 end{bmatrix})
(3.) 求和
咕了。
其他的咕了。
超级跳马
【(description)】
从 ((1,1)) 开始到 ((n,m)) 的方案数 , 节点 ((i,j)) 只能跳到 同行或者相邻行,且列之间的距离应为奇数 。 (nleq 50 , mleq 10^9)
【(solution)】:
参考文献 :题解
首先是一个非常暴力的暴力,我们明白对于节点 (i,j) 它只能够从 (i) 或者 (i-1,i+1) 进行转移,同时列 (j) 只能在奇数列进行转移
所以有一个十分暴力的三重循环
for(int j = 1 ; j <= m ; j++) //枚举列
{
for(int i = 1 ; i <= n ; i++) //枚举行
{
for(int k = j ; k >= 1 ; k-= 2) //只能跳奇数列,同时,可以从同一行进行转移,所以我们 k == i是可以的
{
(f[i][j] += f[k][j] + f[k][j - 1] + f[k][j + 1 ]) %kmod ;
}
}
}
最终答案就是 (f_{n,m} - f_{n,m-2}) ,这里的是一个前缀和的形式,所以我们应该减去前面的方案数,才是 ((n,m)) 本身的 方案数。同样的, 我们可以对答案进行一下魔改,考虑一下 (f_{n,m}) 从何而来,它无法从 (n+1) 而来,所以可以从 (f_{n - 1 , …}) 而来, 同样的,它可以从 (f_{n , m -1}) 和 (f_{n - 1 , m - 1}) ,所以综上, (f_{n,m} = f_{n -1 , m - 1} + f_{n , m - 1}) 转移而来。
同样的这个状态转移也是可以进行魔改的 。
解释一下,就是模仿一下上面,得到了 (f_{i-1,j-1} + f_{i,j-1}) ,那么只需要解释一下 (f_{i+1,j-1}) 和 (f_{i ,j -2}) 即可了,(f_{i+ 1 , j - 1 }) 由于上述的 (f(n,m)) 是无法继续向下的,所以我们不能用 (f_{n+1,m-1}) 来进行标记, 然后 (f_{i , j - 2}) 意味,节点 ((i,j)) 表示可以从同样的行里 , 跳奇数列而来的。
由于我们发现这个状态转移只与 (i-1, i-2) 有关,那么我们就可以类比上面的斐波那契数列,进行矩阵快速幂,加速转移。以 (n = 3) 为例 ,那么也就是
那么
(Code)
/*
by : Zmonarch
知识点 :
*/
#include <algorithm>
#include <iostream>
#include <cstring>
#include <vector>
#include <cmath>
#include <queue>
#include <stack>
#include <map>
#include <set>
#define int long long
#define re register
const int kmaxn = 1e6 + 10 ;
const int kmod = 30011 ;
namespace Base
{
inline int Min(int a , int b) { return a < b ? a : b ; } ;
inline int Max(int a , int b) { return a > b ? a : b ; } ;
inline int Abs(int a ) { return a < 0 ? - a : a ; } ;
};
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
return x * f ;
}
int n , m , len ;
struct Matrix
{
int a[110][110] ;
Matrix()
{
memset(a , 0 , sizeof(a)) ;
}
} ;
Matrix operator * (const Matrix & m1 , const Matrix & m2) //定义矩阵乘法法则
{
Matrix c ;
for(int i = 1 ; i <= len ; i++)
{
for(int j = 1 ; j <= len ; j++)
{
for(int k = 1 ; k <= len ; k++)
{
c.a[i][j] += (m1.a[i][k] * m2.a[k][j] % kmod) ;
c.a[i][j] %= kmod ;
}
}
}
return c ;
}
Matrix quick_pow(Matrix a , int k)
{
Matrix b ;
for(int i = 1 ; i <= len ; i++) b.a[i][i] = 1 ;
while(k)
{
if(k & 1) b = b * a ;
a = a * a ;
k >>= 1 ;
}
return b ;
}
signed main()
{
n = read() , m = read() ;
if(m <= 2)
{
if(n <= 2 && m <= n) printf("1
") ;
else printf("0
") ; return 0 ;
}
len = n << 1 ;
Matrix a ;
for(int i = 1 ; i <= n ; i++)
{
a.a[i][i - 1] = a.a[i][i] = a.a[i][i + n] = a.a[i + n][i] = 1 ;
if(i != n) a.a[i][i + 1] = 1 ;
}
Matrix s = quick_pow(a , m - 2) ;
if(n == 1)
{
printf("%lld
" , s.a[1][1]) ;
return 0 ;
}
int s1 = ( s.a[1][len - 1] + s.a[2][len - 1] + s.a[n + 1][len - 1] ) % kmod ;
int s2 = ( s.a[1][len] + s.a[2][len] + s.a[n + 1][len]) % kmod ;
printf("%lld
" , (s1 + s2) %kmod) ;
return 0 ;
}