zoukankan      html  css  js  c++  java
  • 矩阵

    欢迎来到方块世界。

    definition

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

    [egin{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} imes B_{i,j} ]

    [egin{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} + egin{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 imes m} ]

    [C_{n imes m} = egin{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 imes A_{i,j} ]

    矩阵乘矩阵

    首先我们点明一些东西 :

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

    我们继续给出一个计算式

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

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

    [egin{bmatrix} a & b end{bmatrix} * egin{bmatrix} c & d \ l & m end{bmatrix} = egin{bmatrix} a imes c + b imes l a imes d + b imes 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 imes 2) ,最后我们得到的 (f_i , f_{i+1}) 也是一个 (1 imes 2) 的一个矩阵,从而我们就知道 (M) 这个矩阵为 (2 imes 2) 的,我们设这个矩阵为

    [M_{2 imes 2} = egin{bmatrix} a & b \ c & d \ end{bmatrix} ]

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

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

    所以我们最后就得到了

    [M = egin{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
    " , 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) 矩阵即可。 显然矩阵是三维的,不想推了,反正我是推导出来了。

    [A=egin{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 $(我是没推出来,别问,问就是傻逼) , 得到初始矩阵

    (egin{bmatrix} f_{n } & f_{n - 1} & n & 1 end{bmatrix})

    [base = egin{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)) 只能跳到 同行或者相邻行,且列之间的距离应为奇数 。 (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,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) 为例 ,那么也就是

    [egin{bmatrix} f_{1 , i} & f_{2 , i} & f_{3,i} & f_{1,i-1} &f_{2,i-1} & f_{3,i-1} end{bmatrix} imes A = egin{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 = egin{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
    ") ; 
    		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 ; 
    }
    
  • 相关阅读:
    模仿.Net ThreadPool的线程池控件
    ThreadPool for Delphi
    Thread Pool Engine, and Work-Stealing scheduling algorithm
    Delphi ThreadPool 线程池(Delphi2009以上版本适用)
    Object Pascal对象模型中构造函数之研究
    TExternalThread TThread -- Delphi -- Cannot terminate an externally created thread ?
    Trapping Messages Sent to an Application
    Correct thread terminate and destroy
    Delphi thread exception mechanism
    VCL -- Understanding the Message-Handling System
  • 原文地址:https://www.cnblogs.com/Zmonarch/p/14408268.html
Copyright © 2011-2022 走看看