二进制的秘密--快速幂!

2 篇文章 0 订阅

    最近在学习的过程中碰上这样的问题:如何求N*N?那还不简单,直接一个for呗!(=・ω・=) 不急不急,听我慢慢说来,求的是1000000000次方怎么办?什么?这么变态!!!,当然我们也可以一个一个求,时间复杂度为O(n),你有耐心,OJ没有啊。。。那要怎么办呢?我们自然可以想想更快的方法,今天学习的就是采用神器--二进制来快速求幂!

    快速幂的目的就是快速求幂 ←_← || →_→ 废话。。。那么它是怎么实现的呢?

1.将指数化为2进制,设我们要求10^10,(10)10==(1010)2;

2.10^10==10^(1010)2==10^(1*2^3)*10^(0*2^2)*10^(1*2^1)*10^(0*2^0)==10^8*10^2;

3.不难看出,以上过程就是二进制有1的地方乘对应的次数,没有1的地方相当于乘1,在乘法中相当于没乘!而一个数再怎么大也不过是0,1组成(二进制),逢1就乘,是0就跳过,可以大大节省时间!比如10^10朴素算法要算10次,而快速幂只用算2次;(不考路基数运算的3次)后面会解释什么是基数,一般来说,指数越大,快速幂优势越明显。

4.为什么可以这么算呢?

    首先应该明白,二进制就是逢二进一,每个高位都是低位的2倍,因此当放到指数位上2倍就是平方的关系,比如10^(1010)2,从高位1到低位1依次是10^8,10^2,这两个1差两位,即4倍,平方的平方倍:((2)^2)^2;  因此我们应该设一个基数,来计算相应二进制位上对应的应该是底数的多少次幂:还是以10^(1010)2为例,右往左的二进制位依次对应:10^0,10^2,10^4,10^8;数字代表有多少个,如:(1010)2 代表(从右往左): 0个10^0,    1个10^2,    0个10^4,    1个10^8;     这样相当于有1就乘上,是0就忽略,而二进制只有0和1;所以代码实现就很简单了:

上个模板:

//计算a^b;
int power(int a,int b)
{
    int ans = 1;
    int base = a;
    while(b)
    {
        if(b&1) ans *= base;//该位为1,应该乘对应基数;
        base = base*base;//基数更新;
        b >>= 1;//同b/=2;
    }
    return ans;
}

//计算(a^b)%c;
int power(int a,int b,int c) 
{
    int ans = 1;
    int base = a % c;
    while(b) 
    {
        if(b & 1)
            ans = (ans%c * base%c) % c;
        base = (base%c * base%c) % c;
        b >>= 1;
    }
    return ans;
}

关于这个模板,base是基数,无论该位是否要乘,base都应该变成其平方(因为每一位的基数不一样,高位基数是低位基数的两倍(放在指数上体现为平方)),&是按位与运算,判断该位是否为1(另:&1判断奇偶数要比%2更快), >>是右移运算符,移动一位相当于除以2;

 

 

-------------------------------------------------------华丽的分割线-------------------------------------------------------

 

 

5.我们上几个题目看看:

(1).NYOJ 102 -->次方求模

这题显然要算的数的结果很大,因此不能考虑朴素算法,我们用快速幂,当然也要用到一些其他的定理:

(a*b)%c=(a%c*b%c)%c; (同余定理);

上代码:

#include<stdio.h>
#include<string.h>
int main()
{
    long long a,b,c,i;
    int n;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%lld%lld%lld",&a,&b,&c);
        long long ans=1,temp=a%c;//使a<c;
        while(b!=0)//快速幂;
        {
            if(b&1)//该二进制位为1;
                ans=(ans%c*temp%c)%c;
            temp=(temp%c*temp%c)%c;//每一项(如果存在)都是前一项的平方倍;
            b/=2;//或b>>=1;
        }
        printf("%lld\n",ans);
    }
    return 0;
}
        

主要是注意一下同余定理,不能对结果直接取余(结果也很可能会超过数据的范围,因此直接算出来也不现实)

(2).NYOJ 46--> 最少乘法次数

这个题目其实很有意思,题意说每次相乘的结果都可以用,其实这就暗示了可以用递归,再结合递推,就可以出答案,但是!还有更妙的方法吗?当然有,那就是--快速幂!(〜 ̄△ ̄)〜 我把两个思路都讲一下:

    (2).1. 其实就是分情况讨论,n是奇数,则算第n次方最快的方法是算到n-1次方,再乘一次就是n次方,即N(n)==N(n-1)+1;

为什么?因为偶数可以整除2,每次n都除以2,肯定比你其他方法算更快接近1 (1是递归终点) ;也就是N(n-1)==N((n-1)/2)+1;

之后再讨论N((n-1)/2)的奇偶性,反复直到n==1即可;当n是偶数,就是N(n)==N(n/2)+1;再判断n/2的奇偶性,反复直到n==1即可;

    (2).2. 敲黑板!重点来了!怎么用快速幂算这题呢?首先我们用极端法来考虑,假设我们指数每一次都乘2,即平方,直到不能平方为止,这时我们称得到的数为最大转化数,称平方的次数为最大转化次数。

举个栗子:2^11的最小乘法次数是多少?

我们来算指数的最大转化次数:1 -->2 -->4 -->8 -->16 (16>11,因此最大转化数为8,最大转化次数为3),那我们知道3次就可以乘到2^8,那么要几次可以达到2^11呢?答案是5次,还需2次,我们可以把11拆成二进制来考虑:(11)10==(1011)2;其中最高位的2对应2^3==8;我们已经达到了,剩下两次分别是2^2和2^1,正好对应(1011)2除最高位以外的1!于是我们可得算法:先计算最高位的1对应的次数,再加上剩余位上的1的个数,即为最少乘法次数。

注:这题其实不是严格的快速幂,但是其要求的最少乘法次数原理很像快速幂(都是通过减少乘法次数达到最快求幂),因此可以使用类快速幂思想解题.

上代码:

递推:

#include<stdio.h>//递推;
int main()
{
    int m,n;
    scanf("%d",&m);
    while(m--)
    {
        int count=0;
        scanf("%d",&n);
        if(n==1)
        {
            printf("0\n");
            continue;
        }
        else
        {
            while(n!=1)//循环直到n==1,回归初始状态;
            {
                if(n&1) n--;//n为奇数,2^n=2*2^(n-1),即2^(n-1)的次数+1;
                else n/=2;//n为偶数,2^n=2^(n-1)*2^(n-1),即2^(n-1)的次数+1;
                count++;
            }
            printf("%d\n",count);
        }
    }
    return 0;
}

类快速幂:

#include<stdio.h>//类似快速幂;
int main()
{
    int m,n;
    scanf("%d",&m);
    while(m--)
    {
        int count=0,temp;
        scanf("%d",&n);
        if(n==1)
        {
            printf("0\n");
            continue;
        }
        else
        {
            temp=n;
            while(temp!=1)//最大转化次数;
            {
                temp/=2;
                count++;
            }
            while(n!=1)//快速幂思想;
            {
                if(n&1) count++;//在最大转化数之下,何时需要加;
                n>>=1;
            }
            printf("%d\n",count);
        }
    }
    return 0;
}
        

这里需要说一下,循环结束条件n!=1,是为了不把最高位的1统计进去(最高位已经按位权统计过了)

 

-------------------------------------------------------华丽的分割线-------------------------------------------------------

 

总结一下,快速幂是一个非常有用的小工具,在很多地方都可以用到它,比如:费马小定理求乘法逆元的时候,应该要理解并能够实现快速幂,把它当成一个模块存在脑子里即取即用,数学的路,我还有很长要走啊  (-_-#)

 

  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值