快速幂 矩阵快速幂

前言:好像没啥好写的,链接可能还没有更新完 

快速幂

        快速幂,用于解决a^n当n很大时的情况。通常与取模同时应用。

        用最笨的方法求a^n,即a*a*a*a*....。时间复杂度为O(n),而快速幂(附带取模),可以将时间复杂度降低为O(logn)

        利用倍增的思想,例如3*3*3*3*3*3*3*3,等于(3*3)*(3*3)*(3*3)*(3*3),又等于((3*3)*(3*3))*((3*3)*(3*3)),即3^8 = 9^4 = 81^2,如果n为奇数,a^n = a*a^{[n/2]}*a^{[n/2]},那么换成代码就是:

ll Powermod(ll a,ll b)
{
	ll ans=1;
	a=a%mod;
	while(b>0)
	{
		if(b%2==1)  ans=(ans*a)%mod;
		b=b/2;
		a=(a*a)%mod;
	}
	return ans;
}

        其中的取模操作保证对最终答案取模结果不变,且防止超出数据类型范围。


矩阵快速幂

前置知识:矩阵乘法

        设A为 m*p 的矩阵,Bp*n 的矩阵,那么称m*n的矩阵C为矩阵AB的乘积,记作
C = A*B,其中矩阵C中的第i行第j列元素可以表示为:

        

如下所示:

                                                        (来源:搜狗百科)

        矩阵乘法时间复杂度:

        一个m*n的矩阵和一个n*p的矩阵相乘,将会得到一个m*p的矩阵,

        对于矩阵A(n*m),B(n*p),A表示n行m列的矩阵,A*B的时间复杂度是O(n*m*p),代码对应如下:

//矩阵乘法
void MXMP(int m,int n,int p)//Matrix multiplication
{
    for(int i = 1;i<=m;i++)
        for(int j = 1;j<=p;j++)
            for(int k = 1;k<=m;k++)
                C[i][j] = (C[i][j] + (A[i][k]*B[k][j])%mod)%mod;//防止超界
}

 矩阵快速幂

        矩阵快速幂,即给定一个矩阵A(m*n)),快速计算A^n。一般来说,矩阵快速幂只会涉及方阵即A(n*n),所以这里以方阵为例。

        自己写一个矩阵乘法,然后用快速幂的套进去就是矩阵快速幂。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<vector>
#include<stack>
using namespace std;

typedef long long int ll;
const int mod = (int)1e9+7;
const int MAX_N = 1e3;
int A[MAX_N][MAX_N],res[MAX_N][MAX_N];
int temp[MAX_N][MAX_N];

//A = A*B
void MXMP(int a[][MAX_N],int b[][MAX_N],int n)
{
    //重置临时矩阵temp
    for(int i = 1;i<=n;i++)
        for(int j = 1;j<=n;j++)
            temp[i][j] = 0;

    for(int i = 1;i<=n;i++)
        for(int j = 1;j<=n;j++)
            for(int k = 1;k<=n;k++)
                temp[i][j] = (temp[i][j] + (a[i][k]*b[k][j])%mod)%mod;//防止超界
    
    for(int i = 1;i<=n;i++)
        for(int j = 1;j<=n;j++)
            a[i][j] = temp[i][j];
}

//A = A**x
void PowerMod(int A[][MAX_N],int n,int x)//x为次幂,n为矩阵行/列
{
    memset(res,0,sizeof(res));
    for(int i = 1;i<=n;i++)  res[i][i] = 1;//初始化为单位矩阵
    while(x){
        if(x&1)  MXMP(res,A,n);
        MXMP(A,A,n);
        x >>= 1;
    }
    for(int i = 1;i<=n;i++)
        for(int j = 1;j<=n;j++)
            A[i][j] = res[i][j];
}


int main()
{
    //读入A矩阵
    int n,x;
    cin>>n>>x;//n行n列,x为次幂
    for(int i = 1;i<=n;i++)
        for(int j = 1;j<=n;j++)
            cin>>A[i][j];
    //_______________________________
    PowerMod(A,n,x);
    //检查A矩阵
    for(int i = 1;i<=n;i++){
        for(int j = 1;j<=n;j++){
            cout<<A[i][j]<<" ";
        }
        cout<<endl;
    }
    //________________________________


    return 0;
}

矩阵快速幂的应用

矩阵快速幂本身并不难,难点在于,怎么把一个递推问题转化成矩阵的形式,为什么矩阵快速幂通常只会涉及方阵?通过应用我们才看得出来。

 利用矩阵快速幂求斐波那契数列

         斐波那契数列,f[0] = 1,f[1] = 1,f[i] = f[i-1]+f[i-2],(i>1),换成矩阵乘法的形式,即:

\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} * \begin{pmatrix} f[i-1] \\f[i-2] \end{pmatrix} = \begin{pmatrix} f[i] \\f[i-1] \end{pmatrix}

 再次递推,

\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} * \begin{pmatrix} f[i-2] \\f[i-3] \end{pmatrix} = \begin{pmatrix} f[i-1] \\f[i-2] \end{pmatrix}

那么就可以得到:

\begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}^{n-2} * \begin{pmatrix} f[2] \\f[1] \end{pmatrix} = \begin{pmatrix} f[n] \\f[n-1] \end{pmatrix}

可以手算一下感受一下我们为什么要这样构造矩阵。其实就是递推+乘法结合律。

斐波拉契数列的问题可以拿这道题练手: Problem - 2793 (hdu.edu.cn)

相关题解自己网上搜啊,相对来说算是板子题,题解就不写了。


 熟悉了第一种后,接下来是第二种题型:

计算k次方和--二项式展开

在学习了这种题型后,你就会发现,快速幂矩阵简直妙不可言。太爽了。

这里先贴一个链接,这个博主讲的十分透彻清晰,咱也是在这篇文章里学的。以下有些公式会从这篇博客取过来,如有侵权,立删。 根据递推公式构造系数矩阵用于快速幂_RBS的专栏-CSDN博客

        这类题型主要涉及这样的公式:

        如果不太理解这个式子,这里有一道典型题的链接:

 Recursive sequence - HDU 5950 - Virtual Judge (vjudge.net)

        建议先去看题,尝试着构造构造,构造不出来再来看效果更好。

        现在我们回到上面那个公式,易得S_n = n^k+S_{n-1},关键在于这个n^k,如果用构造斐波拉契数列的思路构造矩阵,你会发现构造不出来,因为底数n本身在不断变化,简单矩阵相乘肯定是不能成功状态转移的。

        所以根本问题是解决这个n^k:将n-1再加1。即S_n = (n-1+1)^k+S_{n-1},将n-1看为一个整体,就变成了二项式(a+b)^k,然后再二项式展开:

 (n-1+1)^k = C_{k}^{0}(n-1)^k+C_{k}^{1}(n-1)^{k-1}+.....+C_{k}^{k}(n-1)^0

X_{n-1} = \begin{pmatrix} (n-1)^k \\ (n-1)^{k-1}\\.\\.\\.\\(n-1)^0\\S_{n-1}\end{pmatrix}

        神奇的地方是,我们对原来的n^k进行状态转移就等于现在将每一项(n-1)变为(n),这就解决了无法对n^k进行状态转移的问题。我们之所以将n-1,也是为了这里的转化。

于是下一个问题是,怎么找到矩阵A进行状态转移。

因为状态转移后,应该变成:

X_n = \begin{pmatrix} (n)^k \\ (n)^{k-1}\\.\\.\\.\\(n)^0\\S_{n}\end{pmatrix}

        然后这里n^k又出现了,再回到那个公式:

n^k = (n-1+1)^k = C_{k}^{0}(n-1)^k+C_{k}^{1}(n-1)^{k-1}+.....+C_{k}^{k}(n-1)^0

        (笑)是不是发现了什么?这里直接给出矩阵A,对照着乘一乘,就反应过来了。

(上面那个水印自动加的,这张图不是我的,小声说一下 )

这时,A*X_{n-1} = X_n. OK,AX矩阵我们现在都找到了,接下来只需要套模板就行了,完美。

题目链接放上面了,很典型的一道题。


其他快速矩阵好题链接:

1.Problem - 3306 (hdu.edu.cn)

题解报告:杭电oj3306:Another kind of Fibonacci题解(矩阵快速幂)_issey的博客-CSDN博客

        网上有许多常用矩阵快速幂公式,这里就不依次列出了。或许刷题时遇到了某些常用的公式会补充上来。

        相关比较经典的题型,边写边补充吧。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Twilight Sparkle.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值