快速幂——幂的二进制分解(如何计算a的n次方)

背景介绍       

        当我们计算一个底数为a的n次幂时,当n过大时,如果我们单纯让n个a相乘,也就是朴素算法,很可能会出现整数溢出的情况,这种做法的时间复杂度为O(n)。而快速幂做法的时间复杂度为O(\log n,可以大大优化求解类似问题的效率。

基本思路

        问题拆解

        快速幂的基本思路是:当n为正整数时,n可以被划分为两类,即偶数与奇数。1.倘若n为偶数,那我们可以将所求a^n转换为求a^{\frac{n}{2}}\times a^{\frac{n}{2}},即{(a^{\frac{n}{2}})}^2;2.倘若n为奇数,我们可以将所求a^n转换为求a^{n-1}\times a,显然n-1为偶数,进而回到指数为偶数的情况。对a^{\frac{n}{2}}我们又可以进行诸如此类的划分······最后我们将问题转换为指数的二进制分解,同时不断对底数进行平方处理,文字上理解比较抽象,我们不妨举个例子:

a^{105}=a^1\times a^{104}=a^1\times ({a^{72}})^2=\cdots =a^1\times a^8\times a^{32}\times a^{64}=a^{2^0+2^3+2^5+2^6}

我们发现这样的分解最后将问题导向某种类似二进制的表示方法,那我们将结果以完整的二进制表示,有

a^{2^0+2^3+2^5+2^6}=({a^{2^0}})^1\times ({a^{2^1}})^0\times({a^{2^2}})^0\times({a^{2^3}})^1\times({a^{2^4}})^0\times({a^{2^5}})^1\times({a^{2^6}})^1

        这不就是咱们的二进制嘛,也就是说我们将指数n分解成了二进制的表示方法,就像这个105被分解成1101001,也就是1+8+32+64,那我们可以对这几个部分分别处理,当运算到1101001的最右端是,我们通过检查这个指数转换为二进制后的数字最后一位是不是1,来判断这个地方需不需要乘a^2,然后将a不断平方,模拟进位。紧接着右移,继续遍历检查二进制位数上1的存在。最后输出结果。

        代码实现

代码表现如下:

long long BinExp(long long a,long ,long long n){
    long long r=1;
    while(n){
    if(n%2==1){
       r=r*a%MOD;
}
       a=a*a%MOD;
       n=int(n/2);//n除以2并向下取整

}
    return r;
}

        一般在这个过程中,数字会比较大,所以需要不断进行取模处理。

        那我们也可以把该过程简化为位与的做法:

long long BinExp(long long a,long long n){
    long long r=1;
    while(n){
    if(n&1==1)
        r=r*a;
    a=a*a;
    n=n>>1;//n的二进制右移一位
}
    return r;

}

 应用方面

以下有矩阵方面应用,当然如果足够熟练,应用的场景可以无限发散,所以以下列出最经典的矩阵应用:

        斐波那契数列

         背景:

        斐波那契数列(Fibonacci sequence)是由意大利数学家莱昂纳多·斐波那契于1202年提出的,其定义为:F(n)=F(n−1)+F(n−2),初始条件为F(0)=0,F(1)=1。

        这个原先是经典的递归问题,但我们可以用快速幂来优化这个过程。

        基本思路:

        那么我们如何优化呢,这个有点考验我们的线性代数功底。我们可以把公式写为:

\binom{f(n+1)}{f(n)}=\binom{​{1} {\ } {1} }{1 \ 0 }\binom{f(n)}{f(n-1)}

        那么根据这个我们再去进行递归,就把原先的二元递归变为了一元递归,要进行多次的递归,我们再利用快速幂思想,也就是:

\binom{f(n+1)}{f(n)}={\binom{1\ 1}{1\ 0}}^n\binom{f(1)}{f(0)}={\binom{1\ 1}{1\ 0}}^n\binom{1}{0}

        对于{\binom{1 \ 1 }{1\ 0}}^n我们就可以用快速幂做啦,对于c++来说没有直接的矩阵相乘的函数,我们需要提前定义矩阵乘法函数,也可以用类的封装做。以下是代码实现:

        代码实现:
                函数代码:
vector<vector<long long>> multiply(const vector<vector<long long>> &A,const vector<vector<long long>> &B,long long MOD){
    int n=A.size()//这里我们默认A和B矩阵为方阵,取n为A和B的行列数
    //如果A和B不为方阵,我们可以这样取A和B和行数和列数
 /* int row=A.size();
    int col=A[0].size();
因为二维矩阵A的形式一般是{{1,0,1},{1,1,1}},那么直接取A.size(),其实就是指行数,取A[0].size就是列数*/
    vector<vector<long long>>C(n,vector<long long>(n,0));//初始化一个大小为n*n的结果矩阵C,初始值全设为0;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            for(int k=0;k<n;k++){
                C[i][j]=(C[i][j]+A[i][k]*B[k][j]%MOD)%MOD;
}
}
}
    return C;
}
//设置一个单位矩阵(类似于整数快速幂里的r=1)
vector<vector<long long>> identity(int n){
     vector<vector<long long>> I(n,vector<long long>(n,0));//初始值为0
     for(int i=0;i<n;i++) I[i][i]=1;//对角线全为1
     return I;
}


//矩阵快速幂矩阵
vector<vector<long long>>matrix_pow(vector<vector<long long>>A,long long pow,long long MOD){
    int n=A.size();
    vector<vector<long long>>res=identity(n);
    while(power>0){
        if(power&1) res=multiply(res,A,MOD);
        A=multiply(A,A,MOD);
        power>>=1;
}
        return res;

}
                类的封装实现:
#include <iostream>
#include <vector>
using namespace std;

const long long MOD=1e9+7;

class Matrix{
    public:
    vector<vector<long long> > mat;
    int rows,cols;
    //构造函数
    Matrix(int r,int c):rows(r),cols(c){
        mat.assign(r,vector<long long>(c,0));
    }
    //单位矩阵构造函数(静态方法)
    static Matrix identity(int n){
        Matrix I(n,n);
        for(int i=0;i<n;i++)I.mat[i][i]=1;
        return I;
    }
    //运算符重载:矩阵乘法
    Matrix operator *(const Matrix& other)const{
        if (cols!=other.rows){
            throw runtime_error("行列不对应");
        }
        Matrix res(rows,other.cols);
        for(int i=0;i<rows;i++){
            for(int j=0;j<other.cols;j++){
                for(int k=0;k<cols;k++){
                    res.mat[i][j]=(res.mat[i][j]+this->mat[i][k]*other.mat[k][j]%MOD)%MOD;
                }
            }
        }
        return res;
    }
    //矩阵快速阵,只能用于方阵
    Matrix pow(long long power)const{
        if(rows!=cols)throw runtime_error("不是方阵");
        Matrix result=Matrix::identity(rows);
        Matrix base=*this;
        while (power>0){
            if(power&1)result =result*base;
            base=base*base;
            power>>=1;
        }
        return result;
    }
    //打印函数
    void print()const{
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                cout<<mat[i][j]<<' ';
            }
            cout<<'\n';
        }
    }
};

int main(){
    int n;
    cin>>n;
    Matrix fib(2,2),chushi(2,1);
    fib.mat={{1,1},{1,0}};
    chushi.mat={{1},{0}};
    Matrix res=fib.pow(n-1);
    res=res*chushi;
    long long Fn=res.mat[0][0]%MOD;
    cout<<Fn<<endl;
}

        

        总的来说,快速幂可以帮我们在很多方面优化效率,尤其是遇到递归类型时,不妨从快速幂的角度思考一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值