矩阵快速幂

by lcx,zjy

基础知识

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

矩阵加减法

矩阵加减法的规则:A±B=C

其中C[i][j]A[i][j]B[i][j]的和或差,即:Cij=Aij±Bij

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

矩阵乘法

矩阵乘法的规则:A×B=C

其中C[i][j] 为A的第i行与B的第j列对应乘积的和,即:Cij=k=1naikbkj

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

这里给个例子帮助理解:

[abcdef][ghijkl]=[ag+bjah+bkai+blcg+djch+dkci+dleg+fjeh+fkei+fl]

交换即是

[ghijkl][abcdef]=[ag+ch+eibg+dh+fiaj+ck+elbj+dk+fl]

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

然后就可以发现,矩阵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];
            }
        }
    }
}

应用

矩阵快速幂加速递推

矩阵快速幂

矩阵幂就是算An

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

证明:

上面两式子都等于

于是——

假设A是nn的矩阵,则有:

A={A,m=1 (Am2)2,m (Am2)2×A,m

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

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

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

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

可以发现:对于任意2×2的矩阵,乘矩阵[1001]值不变,因此可以设其为初始矩阵

由此可推,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;i<N;i++)
        for(int j=0;j<N;j++)
            for(int k=0;k<N;k++)
                ans.z[i][j]=(ans.z[i][j]+a.z[i][k]*b.z[k][j]%mod)%mod;
    return ans;
}
node power(int cnt){//快速幂,只不过底数换成了矩阵
    node ans,A;
    memset(ans.z,0,sizeof(ans.z));
    //A一些赋值 
    for(int i=0;i<N;i++)ans.z[i][i]=1;//ans的赋值 
    while(cnt){
        if(cnt&1)//奇数的话ans*A
            ans=mul(ans,A);
        A=mul(A,A);//A平方
        cnt>>=1;//幂次/2
    }
    return ans;
}

ps:时间复杂度O(n3logm)

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

先看一个简单的例子:

[POJ3070]Fibonacci

在Fibonacci整数序列中,F0 = 0, F1=1,和Fn = Fn1 + Fn2(n≥2).给定整数n(0n109),计算Fn.

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

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

分析题目可以知道:

F[i]=1F[i1]+1F[i2]

F[i1]=1F[i1]+0F[i2]

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

[Fi1Fi2][1110]=[FiFi1]

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

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

(F[i],F[i1])=(F[i1],F[i2])A=(F[i2],F[i3])AA

最后可以得到:(F[n]F[n1])=(F[1],F[0])An1,即:F(n)=F(1)An1

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

于是——

考虑情况:F1n的矩阵,Ann的矩阵,则F=FA也是1n的矩阵

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

Fj=k=1nFkAkj

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

[F1F2F3]×[A11A12A13A21A22A23A31A32A33]=[F1F2F3]
那么如果假设目标状态为G,递推矩阵为A,初始条件为F,则可得出:

G=AnF

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

再看几道题目:

Fibonacci前n项和

Fibonacci数列,f[1]=1,f[2]=1,f[n]=f[n-1]+f[n-2],(n>2),输入n和m,求前n项和模m的值。(1n2×109,1m1×109+10)

 s[n]表示前 n项和,可推出:

s[n]=1s[n1]+1f[n]+0 f[n1]f[n+1]=0 s[n1]+1f[n]+1 f[n1]f[n]=0 s[n1]+1 f[n]+0 f[n1]

因此,可得矩阵:

[ s[n] f[n+1] f[n] ]=[s[n1] f[n] f[n1] ][100111010]

剩下的就和上一题一样了

[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]=2a[i]+b[i]

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

b[i+1]=2a[i]+2b[i]+2c[i]
c[i+1]=b[i]+2c[i]

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

由上可得:

[aibici][220121022]=[ai+1bi+1ci+1]

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

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

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

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

构建矩阵递推的大致套路

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

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

一般F[n]F[n1]都是按照原始递推式来构建的,当然可以先猜一个F[n]

复杂度O(n3logT),T是递归总轮数


矩阵表示修改

[THUSCH2017] 大魔法师

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

1.Ai=Ai+Bi

2.Bi=Bi+Ci

3.Ci=Ci+Ai

4.Ai=Ai+v

5.Bi=Bi×v

6.Ci=v

7.输出i=lrAi,i=lrBi,i=lrCi

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

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

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

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

1.[A,B,C,1]×[1000110000100001]=[A+B,B,C,1]

2,3同理可得

4.[A,B,C,1]×[100001000010v001]=[A+v,B,C,1]

5.[A,B,C,1]×[10000v0000100001]=[A,Bv,C,1]

6.[A,B,C,1]×[10000100000000v1]=[A,B,v,1]

以第一种操作为例子,如果要修改[l,r]中的数据,那就把这段区间全部都乘一个[1000110000100001]就好了,于是就可以用线段树来维护了

[BZOJ2973]石头游戏

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

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

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

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

问t秒(1t109)之后,所有方格中石头最多的格子有多少个石头

问题分析:

以样例为例,设定一维矩阵Ft=[a1 a2 a3 a4 a5 a6]表示t秒时当前每个格子的石子数量,特别的,再加一个a0,使得a0始终为1,所以,转移矩阵Ti0列有且只有第0行为1

初始状态矩阵就是F0=[a0=1 a1=0 a2=0 a3=0 a4=0 a5=0 a6=0]

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

所以可以构造出转移矩阵T1=[1100000010000000010000000100000001000000010000001]

因此也可以通过相同的方法找到T2T3T4T5...

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

Ft=[a(1,1),a(1,2)...a(1,m),a(2,1)...a(n,m)],其中a(i,j)在一维矩阵第(i1)×m+j个位置,令S(i,j)=(i1)×m+j,也再加一个a0,始终为1

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

转移矩阵Ti(1i60)的构造方法:

回顾:状态矩阵Fi所有元素与转移矩阵Ti+1i列所有元素分别相乘的和,得到状态矩阵Fi+1i个元素的数值

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

若操作数字为09,设数值为x,所以TiS(i,j)0x,第S(i,j)S(i,j)1

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

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

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

所以需要将T1T60全部求解出来,令TT=T1×T2×...×T60

t秒后:

状态矩阵Ft=F0TTt60(T1×T2×...×Tr),r=t%60

其中TTt60可以用矩阵快速幂求解,最后求Ft1(n×m)列的最大值即可

又可以发现一个规律:

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


矩阵乘法与邻接矩阵

[TJOI2017]可乐

题目:

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

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

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

1<t106, 1N30,0<M<1001u,vN

分析:

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

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

令该图的邻接矩阵是G,那么我们考虑 G2 是个什么东西

我们单独考虑某一行和某一列的相关运算:令其为 Ga,iGi,bG 为相乘得到的矩阵,那么会有

Ga,b=i=1mGa,iGi,b

容易发现,当且仅当 Ga,iGi,b 都不为零,即i点可连通 a,b 两点的时候上式的该项才为1, 否则为0

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

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

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

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

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

那自爆呢?

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

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

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

最后,统计答案Ans=i=0nA[1][i]

posted @   半口学气!  阅读(438)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示