矩阵快速幂(二分)

矩阵的定义

一个 nm n ∗ m 矩阵是由 nm n ∗ m 个实数排列成n行m列构成的,一般用一对[]括起来,通常用大写字母表示,比如下面 33 3 ∗ 3 的矩阵简记为 A=(aij)33 A = ( a i j ) 3 ∗ 3



矩阵的加减

矩阵之间也可以加减,但是矩阵的加减要求是两个大小相同的矩阵,只有当两个矩阵的行列数相同时,才能进行加减,比如一个n行m列的A矩阵和一个n行m列的B矩阵的加减,就是对应位置元素的加减。

A+B=(aij+bij)nm A + B = ( a i j + b i j ) n ∗ m

AB=(aijbij)nm A − B = ( a i j − b i j ) n ∗ m

具体如下:
这里写图片描述

矩阵的加减满足交换律和结合律

  1. 交换律: A+B=B+A A + B = B + A
  2. 结合律: (A+B)+C=A+(B+C) ( A + B ) + C = A + ( B + C )



矩阵的乘法

矩阵和数相乘

我们定义用实数k右乘矩阵A或者左乘矩阵A,其积等于矩阵A中每个元素乘上k。具体如下

kA=Ak=ka11ka21......kan1ka12ka22......kan2........................ka1mka2m......kanm k A = A k = [ k a 11 k a 12 . . . . . . k a 1 m k a 21 k a 22 . . . . . . k a 2 m . . . . . . . . . . . . . . . . . . . . . . . . k a n 1 k a n 2 . . . . . . k a n m ]

矩阵和矩阵相乘

矩阵A乘上B当且仅当A矩阵的列数和矩阵B行数相同的时候才有定义,即只有 AntBtm A n t · B t m 才有定义。他们相乘得到一个n行m列的矩阵 Cnm C n ∗ m 。C矩阵中每个元素的计算方法为 Cij=ai1b1j+ai2b2j+...+aitbtj=tr=1airbrj C i j = a i 1 b 1 j + a i 2 b 2 j + . . . + a i t b t j = ∑ r = 1 t a i r b r j ,即 Cij C i j 等于A的第i行和B的第j列的每个元素对应相乘的和。比如 A=214356 B=[1423] A = [ 2 3 1 5 4 6 ]   B = [ 1 2 4 3 ] AB=214356[1423]=142128131726 A ⋅ B = [ 2 3 1 5 4 6 ] ∙ [ 1 2 4 3 ] = [ 14 13 21 17 28 26 ]

矩阵的乘法满足结合律但是不满足交换律

  1. A(B+C)=AB+AC A · ( B + C ) = A · B + A · C
  2. (AB)C=A(BC) ( A · B ) · C = A · ( B · C )
  3. ABBA A · B ≠ B · A

代码示例

const int maxn=101;
const int maxm=101;

struct Matrix{
    int n,m;//行 列
    int a[maxn][maxm];
    void clear(){
        n=m=0;
        memset(a,0,sizeof(a));
    }

    Matrix operator +(const Matrix &b) const{
        Matrix tmp;
        tmp.n=n; tmp.m=m;
        for(int i=0;i<n;++i)
            for(int j=0;j<m;++j)
                tmp.a[i][j]=a[i][j]+b.a[i][j];
        return tmp;
    }

    Matrix operator -(const Matrix &b) const{
        Matrix tmp;
        tmp.n=n; tmp.m=m;
        for(int i=0;i<n;++i)
            for(int j=0;j<m;++j)
                tmp.a[i][j]=a[i][j]-b.a[i][j];
        return tmp;
    }

    Matrix operator *(const Matrix &b) const{
        Matrix tmp;
        tmp.clear();
        tmp.n=n; tmp.m=b.m;
        for(int i=0;i<n;++i)
            for(int j=0;j<b.m;++j)
                for(int k=0;k<m;++k)
                    tmp.a[i][j]+=a[i][k]*b.a[k][j];
        return tmp;
    }
};



矩阵的简单应用

矩阵经常被应用到递推、动态规划的转移当中,有一个二维状态数组 dp[n][m] d p [ n ] [ m ] ,有如下转移方程 dp[i][j]=aj1dp[i1][1]+aj2dp[i1][2]+...+ajmdp[i1][m] d p [ i ] [ j ] = a j 1 d p [ i − 1 ] [ 1 ] + a j 2 d p [ i − 1 ] [ 2 ] + . . . + a j m d p [ i − 1 ] [ m ]

实际上我们正好可以用一个矩阵乘法来表示这个过程,A矩阵是一个m*m的矩阵,我们一般叫做状态转移矩阵

dp[i][1]dp[i][2]......dp[i][m]=a[1][1]a[2][1]......a[m][1]a[1][2]a[2][2]......a[m][2]........................a[1][m]a[2][m]......a[m][m]dp[i1][1]dp[i1][2]......dp[i1][m] [ d p [ i ] [ 1 ] d p [ i ] [ 2 ] . . . . . . d p [ i ] [ m ] ] = [ a [ 1 ] [ 1 ] a [ 1 ] [ 2 ] . . . . . . a [ 1 ] [ m ] a [ 2 ] [ 1 ] a [ 2 ] [ 2 ] . . . . . . a [ 2 ] [ m ] . . . . . . . . . . . . . . . . . . . . . . . . a [ m ] [ 1 ] a [ m ] [ 2 ] . . . . . . a [ m ] [ m ] ] [ d p [ i − 1 ] [ 1 ] d p [ i − 1 ] [ 2 ] . . . . . . d p [ i − 1 ] [ m ] ]

第二维都是1,2,…,m,于是可以简记成 dp[i]=Adp[i1] d p [ i ] = A · d p [ i − 1 ]
通过递推可以得到 dp[n]=An1dp[1] d p [ n ] = A n − 1 d p [ 1 ] ,即我们通过矩阵的乘法表示了dp的转移。
而对于乘法可以采用二分快速幂的方法,求得 An1 A n − 1 ,从而计算 dp[n] d p [ n ]



矩阵快速幂

形如 dp[n]=An1dp[1] d p [ n ] = A n − 1 d p [ 1 ] ,如果按照一般递推式来正常得到,时间复杂度是 O(nm2) O ( n m 2 ) 的,n是转移的次数, O(m2) O ( m 2 ) 是一次转移的复杂度。
我们可以先求出 An1 A n − 1 ,同普通数的快速幂一样,矩阵的快速幂也是二分的思想,并注意取模操作,直接看代码

时间复杂度为 O(m3log(n)) O ( m 3 l o g ( n ) )

A=00..0an110..0an201..0an3...............00..1a0,B=fxnfxn+1....fx2fx1 A = [ 0 1 0 . . . 0 0 0 1 . . . 0 . . . . . . . . . . . 0 0 0 . . . 1 a n − 1 a n − 2 a n − 3 . . . a 0 ] , B = [ f x − n f x − n + 1 . . . . f x − 2 f x − 1 ]

代码示例

int solve(int a[],int b[],int m,int t){
    //a是常系数数组 b是初值数组 m是数组大小 t是要求解的项数 从第0项开始 所以共有t+1项
    //m为已知递推式的阶数
    //输出函数在第t项的值f(t)
    //调用矩阵类
    Matrix M,F,res;//M是辅助常数矩阵 F是转移矩阵
    M.clear(),F.clear(),res.clear();
    M.n=M.m=m;
    res.n=res.m=m;
    F.n=m,F.m=1;
    for(int i=0;i<m-1;++i)
        M.a[i][i+1]=1;
    for(int i=0;i<m;++i){
        M.a[m-1][i]=a[i];//a[i]先存小项的系数 即递推式最靠末尾的系数
        F.a[i][0]=b[i];//b[i]先存小项的值 即f0(通常)
        res.a[i][i]=1;//初始化为单位矩阵
    }
    if(t<m) return F.a[t][0];
    for(t-=m-1;t;t/=2){//t-=m-1为还要转移的次数
        if(t&1) res=M*res;
        M=M*M;
    }
    F=res*F;
    return F.a[m-1][0];
}



例题

用 fib(n) 表示斐波那契数列的第 n 项,现在要求你求 fib(n)mod m,fib(1)=1,fib(2)=1。

斐波那契的递推式是 f[n]=f[n1]+f[n2](n>2) f [ n ] = f [ n − 1 ] + f [ n − 2 ] ( n > 2 )
我们构建转移矩阵A A=[0111] A = [ 0 1 1 1 ]

于是有 [f[i1]f[i]]=A[f[i2]f[i1]]=[0111][f[i2]f[i1]] [ f [ i − 1 ] f [ i ] ] = A ⋅ [ f [ i − 2 ] f [ i − 1 ] ] = [ 0 1 1 1 ] [ f [ i − 2 ] f [ i − 1 ] ]

当然矩阵的数的位置可以调换(不唯一),编码时应注意

这样就能将复杂度优化为 O(23lgn) O ( 2 3 l g n )

代码示例

#include<iostream>
#include<string.h>
using namespace std;

const int maxn=101;
const int maxm=101;
int mod;//需要时使用

struct Matrix{
    int n,m;//行 列
    int a[maxn][maxm];//注意函数内数组大小不能超过500000int
    void Clear(){
        n=m=0;
        memset(a,0,sizeof(a));
    }

    Matrix operator +(const Matrix &b) const{
        Matrix tmp;
        tmp.n=n; tmp.m=m;
        for(int i=0;i<n;++i)
            for(int j=0;j<m;++j)
                tmp.a[i][j]=a[i][j]+b.a[i][j];
        return tmp;
    }

    Matrix operator -(const Matrix &b) const{
        Matrix tmp;
        tmp.n=n; tmp.m=m;
        for(int i=0;i<n;++i)
            for(int j=0;j<m;++j)
                tmp.a[i][j]=a[i][j]-b.a[i][j];
        return tmp;
    }

    Matrix operator *(const Matrix &b) const{
        Matrix tmp;
        tmp.Clear();
        tmp.n=n; tmp.m=b.m;
        for(int i=0;i<n;++i)
            for(int j=0;j<b.m;++j)
                for(int k=0;k<m;++k){
                    tmp.a[i][j]+=a[i][k]*b.a[k][j]%mod;
                    tmp.a[i][j]%=mod;
                }
        return tmp;
    }
};

int solve(int a[],int b[],int m,int t){
    //a是常系数数组 b是初值数组 m是数组大小 t是要求解的项数 从第0项开始 所以共有t+1项
    //m为已知递推式的阶数
    //输出函数在第t项的值f(t)
    //调用矩阵类
    Matrix M,F,res;//M是辅助常数矩阵 F是转移矩阵
    M.Clear(),F.Clear(),res.Clear();
    M.n=M.m=m;
    res.n=res.m=m;
    F.n=m,F.m=1;
    for(int i=0;i<m-1;++i)
        M.a[i][i+1]=1;
    for(int i=0;i<m;++i){
        M.a[m-1][i]=a[i];//a[i]先存小项的系数 即递推式最靠末尾的系数
        F.a[i][0]=b[i];//b[i]先存小项的值 即f0(通常)
        res.a[i][i]=1;//初始化为单位矩阵
    }
    if(t<m) return F.a[t][0];
    for(t-=m-1;t;t/=2){//t-=m-1为还要转移的次数
        if(t&1) res=M*res;
        M=M*M;
    }
    F=res*F;//res为最后的常数矩阵
    return F.a[m-1][0];
}


int main()
{
    mod=1000000007;
    int t;
    int a[2],b[2];
    a[0]=a[1]=1;
    b[0]=0;
    b[1]=1;
    while(cin>>t&&t!=-1)
    {
        cout<<solve(a,b,2,t)<<endl;
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值