矩阵
欢迎来到方块世界。
definition
我们可以简单的认为其为一个二维数组,或者说就是将一个矩阵分成一个个小块,其中一个个的块内有数值而已。我们通常用大写字母和其行列来表示当前的矩阵,大概是 \(A _{n\times 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\times B\neq B\times A\)
- 矩阵不满足结合律,即为 :\(A\times(B\times C) \neq (A\times B)\times C\)
- 矩阵满足分配律,,即为: \((A+B)\times C = AC+BC\)
左分配律 :\(A\times (B+C) = AB+AC\) ,右分配律:\((A+B)\times C = AC+BC\)
因为必须满足整个式子的顺序,所以分成了左右两个分配律。
\(M_1\times M_2\) 首先点明这里的顺序是不可换的。我们需要满足的是 :\(,M_1\) 的行要等于 \(m_2\) 的列,我们在下面的计算式子可以体会到。
我们继续给出一个计算式
我们给定一个具体的矩阵 \(1\times 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\times 2\) ,最后我们得到的 \(f_i , f_{i+1}\) 也是一个 \(1\times 2\) 的一个矩阵,从而我们就知道 \(M\) 这个矩阵为 \(2\times 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\n" , k1.a[1][1]) ; 
	std::cout << k1.a[1][1] ; 
}
矩阵加维
\(1.\) 递推式中带有常数项 \(k\)
我们需要单独为常数项 \(k\) 给他开一维,不能只取递推式中带有未知数的值而到了最后加上 \(k\)
举个例子 , 递推式为 $f_{n} = f_{n - 1} + f_{n - 2} + k $
那么我们就可以得到初始矩阵为 $$\begin{bmatrix} f_{n - 2} & f_{n - 1}  & k \end{bmatrix} \times A = \begin{bmatrix} f_{n - 1} & f_{n} & k \ \end{bmatrix}$$
那么这时候我们只需要求出 \(A\) 矩阵即可。 显然矩阵是三维的,不想推了,反正我是推导出来了。
\(2.\)  递推式中带有未知数
同样的,再开一维
举个例子
\(f_{n} = f_{n-1} + f_{n-2} + n\)
首先将未知项的递推式推导出来 $(n) = (n - 1) + 1  $(我是没推出来,别问,问就是傻逼) , 得到初始矩阵
\(\begin{bmatrix} f_{n } & f_{n - 1} & n & 1 \end{bmatrix}\)
\(3.\) 求和
咕了。
其他的咕了。
超级跳马
【\(description\)】
从 \((1,1)\) 开始到 \((n,m)\) 的方案数 , 节点 \((i,j)\) 只能跳到 同行或者相邻行,且列之间的距离应为奇数 。 \(n\leq 50 , m\leq 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\n") ; 
		else printf("0\n") ; 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\n" , 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\n" , (s1 + s2) %kmod) ; 
	return 0 ; 
}

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号