《星空解题家》:二分快速幂——何为二分快速幂?二分思想解题

在这里插入图片描述

在这里插入图片描述

1.第一类:二分快速幂的pow形式(由朴素到快速)

第一类:求pow(a,b)

已知两个正整数,a,b(a,b<=10^5)
求:f(a,b) =a^b

【1】 一步步带你认识二分快速幂

解法一:朴素解法:
在这里插入图片描述
C++/C代码实现:

int f(int a,int b){
     int jie=1;
     while(b--) ans*=a;  //循环b次,每次jie乘上一个a
     return jie;
     }
  1. 暂不考虑溢出问题,仅就速度来看:
  f(2,10)=1024
  f(2,30)=1,073,741,824
  f(2,100)=1,267,650,60,228,229,401,496,703,205,376
  f(2,100000000000)=……………………
  1. 很容易发现,这段算法的时间复杂度为O(n),同时,我们发现一些特点
  • 求a^b过程中不断对a进行重复的乘b
  • 该问题中具有很相似的子问题
  • 线性的重复?很容易想到二分的思想
  • 可不可以将a^b二分成多个子问题,逐一破解
  • 首先:让我们看看一个简单的例子:5^16,你会怎么求?
    1. 第一步: 看到一个5——没必要没必要算
    1. 第二步: 算出5^2 =5 X 5
    1. 第三步: 算出5^4= (5^2) X(5^2)
    1. 第四步: 算出5^8 = (5^4) X (5^4)
    1. 第五步: 算出5^16 = (5^8) X (5^8)
  • 仅仅算了4次,对比原来的16缩了三倍!
  • 假如b再大一点,为1000000000……用二分,我们则可以直接少算b-log(b)次,快了不知多少倍
  • 遇到奇数,我们只需先将以算出的结果X a,再继续二分即可
    在这里插入图片描述

看到这里,理解了5^16(/17)的🌰祝贺你,已经弄懂了二分快速幂。

【2】pow形式二分快速幂的C/C++模板
typedef long long ll;//命别名,方便书写
ll qspow(ll a,ll b){      //递归写法
     if(b==0)return 1;
     ll res=qspow(a*a,(b>>1));   //对正整数右移,等效于/2
     if(b&1) res=res*a;        //b&1,b为奇数则结果为true
     return res;               //为偶数则为false
}
typedef long long ll;
ll qspow(ll a,ll b){    //非递归写法
     ll res=1;
   while(b){
     if(b&1){
     res=res*a;
     }
      a=a*a;
     b>>=1;
   }
 return res;
}

2.第二类:二分快速幂的横幂运算形式(深入研究)

第二类:横幂运算

**已知三个正整数,a,b,c(a,b,c<=10^5),求:
f(a,b,c)=a^b mod c
(mod,在代码中表示为%)

朴素解法:

long long f(long long a,long long b, long long c){
     a=(a%c+c)%c;//🐉对a为负数的情况处理,等价转换,
     long long jie=1%c;
     while(b--) jie=jie%c*a; //先模再乘,保证每次运算后的结果小于模数
     return jie;
}

补充:

  1. 这里求a的b次幂(次方)对c取余数的计算,被称为横幂运算。
  2. 模乘的性质∨
  • ab mod c= (a mod c)(b mod c) mod c
  • 证明思想:记a= r_a +m_a * c;b= r_a + m_a * c,代入左式得ab mod c=r_a*r_b mod c即右式

有了这个性质,我们发现,求横幂运算与第一类无本质差别,只要记得在第一类的每次乘法后%c即可,写成数学表示如下
在这里插入图片描述

【1】 横幂运算式的二分快速幂,代码模板C/C++
typedef long long ll;//对第一类稍加改动即可
ll qspowmod(ll a,ll b,ll c){      //递归写法
     if(b==0)return 1%c;
     ll res=qspowmod(a*a%c,(b>>1),c);
     if(b&1) res=res*a%c;
     return res;
}   
typedef long long ll;
ll qspowmod(ll a,ll b,ll c){    //非递归写法
   ll res=1%c;
   a%=c;             //先把a调小一点           
while(b){
   if(b&1){
   res=(res*a)%c;//❤1️⃣防溢出:可改为res=(res%c)*(a%c)%c
   }
   a=(a*a)%c;//❤2️⃣防溢出:可改为a=(a%c)*(a%c)%c
   b>>=1;
}
return res;
}

注:进行多次a%c与res%c的目的是防止溢出,这是横幂运算的关键一点,尽可能的把转化到更小的乘积结果

【2】 极致:横幂运算式的二分快速幂,再进一步防溢出,优化。
typedef long long ll;
ll qs_mul(ll a,ll b, ll c){ //对乘法优化,防止相乘时所乘结果溢出
    ll jie=0;
    while(b){
         if(b&1)jie=(jie+a)%c;
         b>>=1;
         a=(a+a)%c;  //用二分的加法代替乘法
         }
    return jie;//保证所乘结果永远小于c,防爆!
}
ll qs_pow(ll a,ll b,ll c){//将原来的乘法都用上面的函数代替
      ll res=1;
      while(b){
            if(b&1)res=qs_mul(res,a,c);
            b>>=1;
            a=qs_mul(a,a,c);
            }
     return res;
}

3.分治(二分)思想的体现

  1. 任何可以用计算机求解的问题所需的计算时间都与其规模有关。问题越小,也越容易处理
  2. 分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
  3. 这里,每步二分,将原问题分解成两个子问题,再反复二分,最终将原问题分解成若干个子问题。使得子问题与原问题类型一致而规模却不断缩小,就很容易求出子问题的解,再将各子问题的解综合,就是原问题的解了。
  4. 分治过程——|
    在这里插入图片描述
  5. 原问题是求a^ b,子问题是求a^ n,(n<b),最后合并问题解,由a^1递推知a ^ b.

4.应用实战。

力扣50. Pow(x, n)
力扣372. 超级次方

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

0<Solving)1

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

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

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

打赏作者

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

抵扣说明:

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

余额充值