• 矩阵快速幂


    by lcx,zjy

    基础知识

    矩阵:由$ m\times n$个数排成的m行n列的数表
    其实就是二维数组

    矩阵加减法

    矩阵加减法的规则:\(A\pm B=C\)

    其中$ C[i][j]$ 为\(A[i][j]\)\(B[i][j]\)的和或差,即:$ C_{i j}=A_{ij}\pm B_{ij}$

    因此,相加减的两个矩阵$ A :B$的行列必须相同

    矩阵乘法

    矩阵乘法的规则:\(A\times B=C\)

    其中$ C[i][j]$ 为A的第i行与B的第j列对应乘积的和,即:$ C_{i j}=\displaystyle \sum^n_{k=1}a_{ik}* b_{kj}$

    显然两个相乘是要一行和一列对应乘,那么矩阵乘法是需要A的行数B的列数相等的,这是A*B的前提条件

    这里给个例子帮助理解:

    \(\left[ \begin{matrix} a &b \\c &d\\e&f\end{matrix} \right]* \left[ \begin{matrix} g &h&i \\j &k&l\end{matrix} \right]=\left[ \begin{matrix} ag+bj &ah+bk&ai+bl \\cg+dj &ch+dk&ci+dl\\eg+fj&eh+fk&ei+fl\end{matrix} \right]\)

    交换即是

    $ \left[ \begin{matrix} g &h&i \j &k&l\end{matrix} \right]*\left[ \begin{matrix} a &b \c &d\e&f\end{matrix} \right]=\left[ \begin{matrix} ag+ch+ei &bg+dh+fi\aj+ck+el&bj+dk+fl\end{matrix} \right]$

    可见矩阵的乘法是不满足交换律

    然后就可以发现,矩阵\(C\)的行数应该是\(A\)的行数,列数应该是\(B\)的列数,并且\(C\)也是一个方阵(行数和列数相等的矩阵)

    代码

    int c[N][N];
    void Mul(int a[][N],int b[][N],int n){//n是矩阵大小
        memset(c,0,sizeof(c));
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                for(int k=1;k<=n;k++){
                    c[i][j]+=a[i][k]*b[k][j];
                }
            }
        }
    }
    

    应用

    矩阵快速幂加速递推

    矩阵快速幂

    矩阵幂就是算$ A^n $

    根据矩阵乘法,可以发现矩阵乘法满足结合律:

    证明:

    上面两式子都等于

    于是——

    假设A是$ n*n$的矩阵,则有:

    $ A = \begin{cases} A, & 当m = 1时 \ (A^{\frac m2})^2, & 当m为偶数时 \ (A^{\frac m2})^2\times A, & 当m为奇数时 \end{cases}$

    这个分段函数说明了矩阵快速幂的可行性,然后我们就可以得出算法:

    把快速幂算法中的乘法改成矩阵的乘法就可以了

    不过呢,还有一个问题,ans一开始的初始化是什么?

    ans的初始化就相当于普通快速幂需要初始化为1,即乘上这个矩阵值不改变

    可以发现:对于任意\(2 \times 2\)的矩阵,乘矩阵$ \left[ \begin{matrix} 1 &0 \0 & 1\end{matrix} \right] $值不变,因此可以设其为初始矩阵

    由此可推,ans的初始化就是对角线是1其他全是0

    struct node{
        int z[N][N];
    };
    node mul(node a,node b){//矩阵乘法 
        node ans;
        memset(ans.z,0,sizeof(ans.z));
        for(int i=0;ifor(int j=0;jfor(int k=0;kreturn ans;
    }
    node power(int cnt){//快速幂,只不过底数换成了矩阵
        node ans,A;
        memset(ans.z,0,sizeof(ans.z));
        //A一些赋值 
        for(int i=0;i1;//ans的赋值 
        while(cnt){
            if(cnt&1)//奇数的话ans*A
                ans=mul(ans,A);
            A=mul(A,A);//A平方
            cnt>>=1;//幂次/2
        }
        return ans;
    }
    

    ps:时间复杂度$ O(n^3logm)$

    那么我们应该怎么加速递推呢?

    先看一个简单的例子:

    [POJ3070]Fibonacci

    在Fibonacci整数序列中,\(F_0\) = 0, \(F_1\)=1,和\(F_n\) = \(F_{n−1}\) + \(F_{n−2}\)(n≥2).给定整数n\((0≤n≤10^9)\),计算\(F_n\).

    1.列解析式:显然:$ F(n)=F(n-1)+F(n-2)$,但这个数据范围就不是很显然了。

    2.建立矩阵递推式,找到转移矩阵:

    分析题目可以知道:

    $F[i]=1*F[i-1]+1 *F[i-2] $

    \(F[i-1]=1*F[i-1]+0 *F[i-2]\)

    将以上两个式子结合可得:

    \(\left[ \begin{matrix} F_{i-1} &F_{i-2} \end{matrix} \right]*\left[ \begin{matrix} 1 &1 \\1 &0 \end{matrix} \right]=\left[ \begin{matrix} F_i &F_{i-1}\end{matrix} \right]\)

    简写成$ F(n-1)*A=F(n)$,A矩阵就是那个2*2的常数矩阵

    我们还可以把上述式子转换一下:

    \(( F[i] , F[i-1] )=( F[i-1] ,F[i-2] )*A=( F[i-2] ,F[i-3] ) *A *A\)

    最后可以得到:\((F[n] F[n-1])=(F[1] ,F[0] )*A^{n-1}\),即:\(F(n)=F(1) *A^{n-1}\)

    就愉快的转换成算矩阵快速幂了

    于是——

    考虑情况:$ F$是\(1*n\)的矩阵,$ A\(是\)nn\(的矩阵,则\)F'= FA$也是\(1*n\)的矩阵

    \(F\)\(F'\)可以看作是一维数组,省略他们的行下标1,按照矩阵乘法的定义,有:

    $ F'j=\displaystyle\sum{k=1}^nF_k*A_{kj}$

    可以认为,通过乘上矩阵\(A\),从原始状态\(F\)递推到了\(F'\)状态:

    \(\left[ \begin{matrix} F_1 &F_2 &F_3 \end{matrix} \right]\times \left[ \begin{matrix} A_{11} &A_{12} &A_{13} \\A_{21} &A_{22} &A_{23} \\A_{31} &A_{32} &A_{33}\end{matrix} \right]=\left[ \begin{matrix} F'_1 &F'_2 &F'_3\end{matrix} \right]\)
    那么如果假设目标状态为\(G\),递推矩阵为\(A\),初始条件为\(F\),则可得出:

    \(G=A^n*F\)

    因为我们已经会了矩阵快速幂算法,所以唯一需要我们考虑的问题就是如何构造递推矩阵\(A\)

    再看几道题目:

    Fibonacci前n项和

    Fibonacci数列,f[1]=1,f[2]=1,f[n]=f[n-1]+f[n-2],(\(n>2\)),输入n和m,求前n项和模m的值。(\(1\leq n\leq 2\times 10^{9}\),\(1\leq m\leq 1\times 10^9+10\))

    设$ \ s[n]\(表示前\) \ n $项和,可推出:

    $s[n]=1 * s[n-1]+1* f[n]+0\ f[n-1]\f[n+1]=0\ s[n-1]+1f[n]+1\ f[n-1]\f[n]=0 \ s[n-1]+1\ f[n]+0*\ f[n-1] $

    因此,可得矩阵:

    $[\ s[n]\ f[n+1]\ f[n]\ ]=[s[n-1]\ f[n]\ f[n-1]\ ]*\left[ \begin{matrix} 1 & 0 & 0 \1 & 1 & 1\0 &1 & 0 \end{matrix} \right] $

    剩下的就和上一题一样了

    [POJ3734]方块涂色

    N个方块排成一列 用红,蓝,绿,黄4种颜色去涂色,求红色方块 和绿色方块个数同时为偶数的 方案数 对10007取余

    1.列解析式

    先定义状态分析递推式:假设已涂完前i个方块,有:
    $ a[i]\(表示从1~i的方块中,红、绿方块数量都是偶数的方案数 \)b[i]\(表示从1~i的方块中,红、绿方块数量一个是偶数一个是奇数的方案数 \)c[i]$表示从1~i的方块中,红、绿方块数量都是奇数的方案数
    初始:a(0)=1; b(0)=0; c(0)=0

    分析a数组递推过程:

    1.到i时红和绿的方格个数都是偶数,且i+1个方块被染成了蓝或黄色

    2.到i时红和绿的方格个数一偶一奇,

    且i+1个方块被染成了奇数个所对应的颜色

    可得:\(a[i+1]=2*a[i]+b[i]\)

    b与c的分析如上,可得:

    \(b[i+1]=2*a[i]+2*b[i]+2*c[i]\)
    \(c[i+1]=b[i]+2*c[i]\)

    2.建立矩阵递推式,找到转移矩阵:

    由上可得:

    $\left[ \begin{matrix} a_i&b_i&c_i\end{matrix} \right] *\left[ \begin{matrix} 2&2&0 \1&2&1\0 &2& 2 \end{matrix} \right]=\left[ \begin{matrix} a_{i+1}&b_{i+1}&c_{i+1}\end{matrix} \right] $

    矩阵快速幂加速递推题目特点

    1.可以抽象为长度为n的一维数组(即状态矩阵),矩阵在单位时间内变化一次

    2.变化的形式是线性递推(只有若干”加法“或“乘以一个系数”的运算)

    3.递推轮数大,但矩阵长度n不大

    构建矩阵递推的大致套路

    上文常数矩阵$ A \(就叫做**转移矩阵**,它能把\) F[n-1] \(转移到\) F[n] \(;然后这就是个等比数列,直接写出通项\) F[n]=A^{n-1}*F[1]\(此处\) f[1] $叫初始矩阵。

    关键在于定义出状态矩阵和转移矩阵。

    一般$ F[n]\(与\)F[n-1] \(都是按照原始递推式来构建的,当然可以先猜一个\) F[n] $。

    复杂度$ O(n^3logT)\(,\)T $是递归总轮数


    矩阵表示修改

    [THUSCH2017] 大魔法师

    题目大意:n颗球,一颗球里有三个数\(A\) \(B\) \(C\)。有m次操作,每次操作选择一个区间\([l,r]\)进行一下七种操作之一:

    1.\(A_i=A_i+B_i\)

    2.\(B_i=B_i+C_i\)

    3.\(C_i=C_i+A_i\)

    4.\(A_i=A_i+v\)

    5.\(B_i=B_i\times v\)

    6.\(C_i=v\)

    7.输出\(\displaystyle\sum_{i=l}^rA_i\),\(\displaystyle\sum_{i=l}^rB_i\),\(\displaystyle\sum_{i=l}^rC_i\)

    对于区间修改,我们第一想法是线段树。但是每次修改都与该点中其他属性有关,故不能整体修改

    于是就想矩阵乘法来改变状态:

    把一颗球看作一个\(1\times 4\)的矩阵\([A,B,C,1]\)(最后一个\(1\)用来维护常项)

    于是我们可以很轻易的推出转移矩阵:

    1.\([A,B,C,1]\times \begin{bmatrix} 1 & 0&0&0 \\ 1 & 1&0&0\\0&0&1&0\\0&0&0&1 \end{bmatrix}=[A+B,B,C,1]\)

    2,3同理可得

    4.\([A,B,C,1]\times \begin{bmatrix} 1 & 0&0&0 \\ 0 & 1&0&0\\0&0&1&0\\v&0&0&1 \end{bmatrix}=[A+v,B,C,1]\)

    5.\([A,B,C,1]\times \begin{bmatrix} 1 & 0&0&0 \\ 0 & v&0&0\\0&0&1&0\\0&0&0&1 \end{bmatrix}=[A,B*v,C,1]\)

    6.\([A,B,C,1]\times \begin{bmatrix} 1 & 0&0&0 \\ 0 & 1&0&0\\0&0&0&0\\0&0&v&1 \end{bmatrix}=[A,B,v,1]\)

    以第一种操作为例子,如果要修改\([l,r]\)中的数据,那就把这段区间全部都乘一个\(\begin{bmatrix} 1 & 0&0&0 \\ 1 & 1&0&0\\0&0&1&0\\0&0&0&1 \end{bmatrix}\)就好了,于是就可以用线段树来维护了

    [BZOJ2973]石头游戏

    大意:有一个\(n\)\(m\)\((0\leq n,m\leq 8)\)的矩阵,还有一个与之对应的\(n\)\(m\)列操作序列,一共有\(act\)种操作序列,编号\(0到(act-1)\)\((0\leq act\leq10)\)每一种操作序列都是长度不超过6,循环执行,一秒一个,所有格子同时进行包括:

    数字0-9:拿0-9个石头到该格子

    NWSE:把这个格子内所有的石头推到相邻的格子,N表示上方,W表示左方,S表示下方,E表示右方

    D:拿走这个格子的石头。

    问t秒\((1\leq t\leq10^9)\)之后,所有方格中石头最多的格子有多少个石头

    问题分析:

    以样例为例,设定一维矩阵\(F_t=[a_1\ a_2\ a_3\ a_4\ a_5\ a_6]\)表示\(t\)秒时当前每个格子的石子数量,特别的,再加一个\(a_0\),使得\(a_0\)始终为\(1\),所以,转移矩阵\(T_i\)\(0\)列有且只有第\(0\)行为\(1\)

    初始状态矩阵就是\(F_0=[a_0=1\ a_1=0\ a_2=0\ a_3=0\ a_4=0\ a_5=0\ a_6=0]\)

    第一秒的操作为\(1,E,E,E,E,0\),第1个格子+1,第2,3,4,5个格子推向右方,第6个格子不移动不添加

    所以可以构造出转移矩阵\(T_1=\begin{bmatrix} 1 & 1&0&0&0&0&0 \\ 0 & 1&0&0&0&0&0\\0&0&0&1&0&0&0\\0&0&0&0&1&0&0\\0&0&0&0&0&1&0\\0&0&0&0&0&0&1 \\0&0&0&0&0&0&1 \end{bmatrix}\)

    因此也可以通过相同的方法找到\(T_2\)\(T_3\)\(T_4\)\(T_5\)...

    因为\(n\)\(m\)的数据范围较小,所以我们可以把\(n\)\(m\)列的网络转化为长度为\(n\times m\)的一维矩阵

    \(F_t=[a_{(1,1)},a_{(1,2)}...a_{(1,m)},a_{(2,1)}...a_{(n,m)}]\),其中\(a_{(i,j)}\)在一维矩阵第\((i-1)\times m+j\)个位置,令\(S(i,j)=(i-1)\times m+j\),也再加一个$ a_0$,始终为\(1\)

    因为每个操作序列的长度不超过\(6\),且\(1-6\)的最小公倍数为\(60\),所以每经过\(60\)秒,操作序列又会从最开始的字符开始,因此需要构造\(60\)\((n\times m+1)\times (n\times m+1)\)转移矩阵\(T\),包含第\(0-(n\times m)\)行和第\(0-(n\times m)\)

    转移矩阵\(T_i(1\leq i\leq 60)\)的构造方法:

    回顾:状态矩阵\(F_i\)所有元素与转移矩阵\(T_{i+1}\)\(i\)列所有元素分别相乘的和,得到状态矩阵\(F_{i+1}\)\(i\)个元素的数值

    注:以下操作均不计除了当前石子外,其他石子的操作对此石子的影响

    若操作数字为\(0-9\),设数值为\(x\),所以\(T_i\)\(S(i,j)\)\(0\)\(x\),第\(S(i,j)\)\(S(i,j)\)\(1\)

    若为字符\(N\),则转移矩阵第\(S(i,j)\)\(S(i-1,j)\)\(1\),字符\(W,S,E\)类似

    若为字符\(D\),则转移矩阵此列不做处理

    为了保证\(F_i(0)\)始终为\(1\),所有转移矩阵\(T_i\)\(0\)列有且只有第\(0\)行为\(1\)

    所以需要将\(T_1-T_{60}\)全部求解出来,令\(TT=T_1\times T_2\times ...\times T_{60}\)

    \(t\)秒后:

    状态矩阵\(F_t=F_0*TT^{\frac{t}{60}}*(T_1\times T_2\times ...\times T_r),r=t\%60\)

    其中\(TT^{\frac{t}{60}}\)可以用矩阵快速幂求解,最后求\(F_t\)\(1-(n\times m)\)列的最大值即可

    又可以发现一个规律:

    如果在应用矩阵乘法时,遇到常数项,经常需要在“状态矩阵”中添加一个额外的位置,始终储存常数\(1\),并乘上“转移矩阵”中适当的系数,累加到“状态矩阵”的其他位置


    矩阵乘法与邻接矩阵

    [TJOI2017]可乐

    题目:

    加里敦星球的人们特别喜欢喝可乐。因而,他们的敌对星球研发出了一个可乐机器人,并且放在了加里敦星球的\(1\)号城市上。这个可乐机器人有三种行为: 停在原地,去下一个相邻的城市,自爆。它每一秒都会随机触发一种行为。现在给加里敦星球城市图,在第\(0\)秒时可乐机器人在\(1\)号城市,问经过了\(t\)秒,可乐机器人的行为方案数是多少?

    \(N\)表示城市个数,\(M\)表示道路个数。

    保证两座城市之间只有一条路相连,且没有任何一条道路连接两个相同的城市。

    $ 1 < t \leq 10^6, $ \(1≤N≤30,0

    分析:

    先用邻接矩阵存图(两个点之间若有边则\(A[u][v]=1\))

    如果我们没有在原地停留和自爆两个操作,那么就是问从起点出发,走t步的不同路径数

    令该图的邻接矩阵是\(G\),那么我们考虑 \(G^2\) 是个什么东西

    我们单独考虑某一行和某一列的相关运算:令其为 $ G_{a,i}$和 $ G_{i,b}$令 \(G′\) 为相乘得到的矩阵,那么会有

    $ G'{a,b}=\displaystyle \sum^m{i=1} G_{a,i}*G_{i,b}$

    容易发现,当且仅当 $ G{a,i}$ 和 \(G{i,b}\) 都不为零,即\(i\)点可连通 \(a,b\) 两点的时候上式的该项才为\(1\), 否则为\(0\)

    那么所有的这些情况累加起来,就是从\(a\)\(b\)长度为\(2\)的路径条数(即方案数)

    所以,\(G^2\)得到的矩阵其实表示了任意两点间长度为2的方案数

    (也从\(floyd\)算法的角度考虑)那么不难发现\(G^k\)的第\(i\)行第\(j\)列的数字含义是从\(i\)\(j\)经过\(k\)步的路径方案总数

    那么在原地停留和自爆怎么处理?

    在原地停留很简单,我们只要认为每个点都有一个从自己到自己的自环即可。

    那自爆呢?

    我们可以将自爆这个状态也看成一个城市,就设它为编号\(0\)

    我们在邻接矩阵上从每个点都向这个点连一条边,这个点除了自己外不连其他出边。

    这样就满足了任何一个点随时可以自爆,且无法恢复到其他状态。

    最后,统计答案\(Ans=\sum\limits_{i=0}^{n}A[1][i]\)

  • 相关阅读:
    Selenium WebDriver - 其它
    UG\NX二次开发 获取工作部件的事例 UF_ASSEM_ask_work_occurrence
    【VScode】保存文件自动按照eslint规范格式化
    定点小数和定点整数的取值范围
    AIGC(生成式AI)试用 4 -- 从模糊到精确
    Python计时库——Time库的使用详解
    MViTv2:Facebook出品,进一步优化的多尺度ViT | CVPR 2022
    docker用法
    docker-compose安装RabbitMQ
    6234. 最小公倍数为 K 的子数组数目
  • 原文地址:https://www.cnblogs.com/lefy959/p/16794480.html