矩阵

欢迎来到方块世界。

definition

我们可以简单的认为其为一个二维数组,或者说就是将一个矩阵分成一个个小块,其中一个个的块内有数值而已。我们通常用大写字母和其行列来表示当前的矩阵,大概是 \(A _{n\times m}\) 这个鸭子的。 其意思就是 \(n\)\(m\) 列的一个矩阵 \(A\)

\[\begin{bmatrix} a_{1 ,1} & a_{1,2} & a_{1,3} & \cdots & a_{1,m} \\ a_{2 , 1} & a_{2,2} & a_{2,3} & \cdots & a_{2,m} \\ \vdots & \vdots & \vdots \ddots & \vdots \\ a_{n,1} & a_{n,2} & a_{n,3} & \cdots & a_{n,m} \\ \end{bmatrix} \]

给出定义矩阵的Code , 这里选用结构体

struct Matrix {
	int n , m ; 
	int a[kmaxn][kmaxn] ; 
	Matrix() 
	{
		n = m = 0 ; 
		memset(a , 0 , sizeof(a)) ; 
	}
};

Base-operational rule

运算律的英文真的是绝了。日。

加减法的运算是一样的。这里只用加法来表示 。

\[C_{i,j} = A_{i,,j} \times B_{i,j} \]

\[\begin{bmatrix} a_{1 ,1} & a_{1,2} & \cdots & a_{1,m} \\ a_{2 , 1} & a_{2,2} & \cdots & a_{2,m} \\ \vdots & \vdots & \vdots \ddots & \vdots \\ a_{n,1} & a_{n,2} & \cdots & a_{n,m} \\ \end{bmatrix} + \begin{bmatrix} b_{1 ,1} & b_{1,2} & \cdots & b_{1,m} \\ b_{2 , 1} & b_{2,2} & \cdots & b_{2,m} \\ \vdots & \vdots & \vdots \ddots & \vdots \\ b_{n,1} & b_{n,2} & \cdots & b_{n,m} \\ \end{bmatrix} = C_{n\times m} \]

\[C_{n\times m} = \begin{bmatrix} a_{1 ,1} + b_{1,1}& a_{1 ,2} + b_{1,2} & \cdots & a_{1,m} + b_{1,m}\\ a_{2 ,1} + b_{2,1}& a_{2 ,2} + b_{2,2} & \cdots & a_{2,m} + b_{2,m} \\ \vdots & \vdots & \vdots \ddots & \vdots \\ a_{n ,1} + b_{n,1}& a_{n ,2} + b_{n,2} & \cdots & a_{n,m} + b_{n,m}\\ \end{bmatrix} \]

由于这玩意放在博客园实在是放不开了,就直接拆开了.

减法同样,无非是将上文的 + 换成了 - 而已。

几种分类。

单位矩阵:

对角线上的点全部为 \(1\)

零矩阵 :

矩阵所有的元素全部为 \(0\)

对称阵

\(……\) 其他的好像用不到。

Mul_operational rule

首先说明矩阵乘法和向量一样,支持数乘矩阵,和矩阵乘矩阵。

数乘矩阵

数乘矩阵的话就是这样的(这里不给出矩阵了,\(yy\) 一下,真的好麻烦的)

\[C_{i,j} = num \times A_{i,j} \]

矩阵乘矩阵

首先我们点明一些东西 :

  • 矩阵不满足交换律,即为 : \(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\) 的列,我们在下面的计算式子可以体会到。

我们继续给出一个计算式

\[C_{i,j} = \sum_{k = 1}^{m_A}(A_{i,k}\times B_{k,j}) \]

我们给定一个具体的矩阵 \(1\times 2\) (因为后面有提到斐波那契数列)

\[\begin{bmatrix} a & b \end{bmatrix} * \begin{bmatrix} c & d \\ l & m \end{bmatrix} = \begin{bmatrix} a\times c + b\times l \ \ \ \ \ \ \ \ a\times d + b\times m \end{bmatrix} \]

这里给出矩阵乘法的代码:

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\) 的,我们设这个矩阵为

\[M_{2\times 2} = \begin{bmatrix} a & b \\ c & d \\ \end{bmatrix} \]

然后我们根据矩阵乘法就能够得到如下的一个式子。

\[ \begin{cases} af_{i - 1} + cf_{i} = f_i\\bf_{i-1} + df_i = f_{i+1} \end{cases} \]

所以我们最后就得到了

\[M = \begin{bmatrix} 0 & 1\\ 1 & 1\\ \end{bmatrix} \]

然后我们就那么递推就行了;
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\) 矩阵即可。 显然矩阵是三维的,不想推了,反正我是推导出来了。

\[A=\begin{bmatrix} 0& 1 & 0 \\1 &1& 0\\0 & 0 & 1 \end{bmatrix} \]

\(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}\)

\[base = \begin{bmatrix} 1 & 1 & 0 & 0 \\ 1 & 0 &0 &0 \\1 & 0 & 1 & 0 \\ 1 & 0 & 1 & 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,j} = f_{i,j - 1 } + f_{i , j - 2} + f_{i + 1 , j - 1} + f_{i - 1 , j - 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\) 为例 ,那么也就是

\[\begin{bmatrix} f_{1 , i} & f_{2 , i} & f_{3,i} & f_{1,i-1} &f_{2,i-1} & f_{3,i-1} \end{bmatrix} \times A = \begin{bmatrix} f_{1 , i+1} & f_{2 , i+1} & f_{3,i+1} & f_{1,i} &f_{2,i} & f_{3,i} \end{bmatrix} \]

那么

\[A = \begin{bmatrix} 1 & 1 & 0 \\ 1 & 1 & 1 \\ 0 & 1 & 1 \end{bmatrix} \]

\(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 ; 
}
posted @ 2021-02-17 09:50  SkyFairy  阅读(310)  评论(4编辑  收藏  举报