二分幂,快速幂,矩阵快速幂,快速乘

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/MosBest/article/details/69264953

前言

二分幂,快速幂,矩阵快速幂在算大指数次方时是很高效的。

求 a^n 的值是多少?n是1到10^18次方的一个整数。
  求一个数的n次方,朴素的算法就是直接for循环,一遍一遍的乘,a*a*a*a*a*a… …,O(N)的复杂度。此时,如果n很小的话,并没有什么影响。
  但是当n非常大,n=10^18,O(N)也会超时,那么需要更快的算法,二分幂算法 和 快速幂算法。
而对于矩阵的高次幂,则将快速幂算法改进为矩阵快速幂算法。
最后讲一下,在大数情况下的乘法。

二分幂

要求 a^n,如果知道了 a^(n/2) 次方的话,再来个平方就可以了。

如果n是偶数,则A=a^(n/2) ; A=A*A.。
如果n是奇数 , 则A=a^((n-1)/2) ; A=a*A*A。
这就一下子差不多就节省了n/2-1次乘法运算
  那么按照这个思路就能运用分治的思想,那么复杂度就有原来的O(n),降低为O(lgn)。

long long int pow(int a,int n)//求a的n次幂
{
    if (n==0)
        return 1;
    if (n==1)
        return a;
    long long int ans=pow(a,n/2);//从函数的功能区理解递归
    ans*=ans;
    if (n%2==1)
        ans*=a;
    return ans;
}

快速幂算法

快速幂 , 矩阵快速幂 在算大指数次方时是很高效的,他的基本原理是二进制。快速乘也是用了二进制。

大家首先要认识到这一点:任何一个整数N,都能用二进制来表示。。
那么对于a^n , n一定可以用二进制表示。
比如a^156,而156(10)=10011100(2)
那么
A=a156=a10011100
=a271+260+250+241+231+221+210+200
=(a271)(a260)(a250)(a241)(a231)(a221)(a210)(a200)
我们就按照这个公式来求解a156,原来要进行156-1=155次乘法运算,现在的差不多运算次数就是他 二进制的长度*二进制中1的个数=8*4=24次

long long int fun( int a, int b ) 
{
    long long int r = 1;
    int base = a;
    while( b != 0 ) 
    {
        if(b & 1)//判断奇偶性
        {
            r *= base;

        }
         base *= base; //注意:a^{2^7}=a^{2^6} * a^{2^6} ,而不是 a^{2^7}=a^{2^6} * a ,所以这是对的。
        b /= 2;//与b=b>>1相同
    }
    return r;
}

矩阵快速幂算法

可能你会问了这个算法有什么用呢?其实用的更多是使用矩阵快速幂,算递推式,注意是递推式 ,比如 f(n)=a*f(n-1)+b*f(n-2),简单的如斐波那契数列的第一亿项的结果模上10000000后是多少你还能用递推式去,逐项递推吗?当然不能,这里就可以发挥矩阵快速幂的神威了,那斐波那契数列和矩阵快速幂能有一毛钱的关系?答案是有而且很大
对于f(n)=a*f(n-1)+b*f(n-2) ,
我们可以考虑矩阵这种数学工具,构造矩阵
这里写图片描述

这样求f(n),f(n-1) 就相当于求左边矩阵的n-2次幂。这个时候就可以用上面的快速幂来计算了。
代码与快速幂类似,只是实数乘法变成了矩阵乘法。用个函数写就行了。

粘贴一个求斐波那契数列f(n) 的代码

# include<cstdio>  
# include<cstring>  
using namespace std;  
#define NUM 50  
int MAXN,n,mod;  
struct Matrix//矩阵的类  
{  
    int a[NUM][NUM];  
    void init()           //将其初始化为单位矩阵  
    {  
        memset(a,0,sizeof(a));  
        for(int i=0;i<MAXN;i++)  
            a[i][i]=1;  
    }  
} A;  
Matrix mul(Matrix a,Matrix b)  //(a*b)%mod  矩阵乘法  
{  
    Matrix ans;  
    for(int i=0;i<MAXN;i++)  
        for(int j=0;j<MAXN;j++)  
        {  
            ans.a[i][j]=0;  
            for(int k=0;k<MAXN;k++)  
                ans.a[i][j]+=a.a[i][k]*b.a[k][j];  
            ans.a[i][j]%=mod;  
        }  
    return ans;  
}  

Matrix add(Matrix a,Matrix b)  //(a+b)%mod  //矩阵加法  
{  
    int i,j,k;  
    Matrix ans;  
    for(i=0;i<MAXN;i++)  
        for(j=0;j<MAXN;j++)  
        {  
            ans.a[i][j]=a.a[i][j]+b.a[i][j];  
            ans.a[i][j]%=mod;  
        }  
    return ans;  
}  

Matrix pow(Matrix a,int n)    //(a^n)%mod  //矩阵快速幂  
{  
    Matrix ans;  
    ans.init();  
    while(n)  
    {  
        if(n%2)//n&1  
            ans=mul(ans,a);  
        n/=2;  
        a=mul(a,a);  
    }  
    return ans;  
}  

Matrix sum(Matrix a,int n)  //(a+a^2+a^3....+a^n)%mod// 矩阵的幂和  
{  
    int m;  
    Matrix ans,pre;  
    if(n==1)  
        return a;  
    m=n/2;  
    pre=sum(a,m);                      //[1,n/2]  
    ans=add(pre,mul(pre,pow(a,m)));   //ans=[1,n/2]+a^(n/2)*[1,n/2]  
    if(n&1)  
        ans=add(ans,pow(a,n));          //ans=ans+a^n  
    return ans;  
}  

void output(Matrix a)//输出矩阵  
{  
    for(int i=0;i<MAXN;i++)  
        for(int j=0;j<MAXN;j++)  
            printf("%d%c",a.a[i][j],j==MAXN-1?'\n':' ');  
}  
int main()  
{  
    freopen("in.txt","r",stdin);  
    Matrix ans;  
    scanf("%d%d%d",&MAXN,&n,&mod);  
    for(int i=0;i<MAXN;i++)  
        for(int j=0;j<MAXN;j++)  
        {  
            scanf("%d",&A.a[i][j]);  
            A.a[i][j]%=mod;  
        }  
    ans=sum(A,n);  
    output(ans);  
    return 0;  
}  

快速乘

求a*b%m , 当a*b结果很大,乘完后可能会移除。
可以用二进制来实现快速乘算法。
以前十进制的乘法是: 123*567=123*5*100 + 123*6*10 + 123 * 7 * 1
这里100,10,100 都是十进制 的进制位数。那么如果考虑二进制的话,我们任选其他任意二进制数,就有
100110111010=1001101241+1001101231+1001101220+1001101211+1001101200
我们对上面的每一个加项进行取模,在加起来,就不会溢出了。

long long int fun(long long int a ,long long int b , long long int m)
{
    int sum=0;
    int k=1;
    while(b)
    {

        if(b&1)
        {
            sum=(sum+a*k)%m;
        }
        k=(k*2)%m;
        b=b/2;
    }
}

引用原句
在我就说下我对二进制的感想吧:
我们在做很多”连续“的问题的时候都会用到二进制将他们离散简化
1.多重背包问题
2.树状数组
3.状态压缩DP
……………还有很多。。。究其根本还是那句话:化连续为离散。。很多时候我们并不是为了解决一个问题而使用二进制,更多是时候是为了优化而使用它。所以如果你想让你的程序更加能适应大数据的情况,那么学习学习二进制及其算法思想将会对你有很大帮助。

展开阅读全文

没有更多推荐了,返回首页