快速幂第一章(C++递归版本)

快速幂第一章(C++递归版本)

当题目要我们求类如 a b = ? a^b=? ab=?的题目时,最直接,最暴力的写法如下:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
int main() {
    LL a,b,res=1;
    cin>>a>>b;
    while(b--) {
        res*=a;
    }
    cout<<res;
}

算法简单明了,循环 b b b次,每次答案 × a ×a ×a。而这类代码,往往是TLE。

TLE很好理解。这段代码的时间复杂度是 O ( b ) O(b) O(b)。当b的数据范围很大时自然会超时。

时间复杂度如何计算可以参考编者的另一篇博客 O O O时间复杂度详解


关于幂的知识

  1. a b = a × a × . . . × a × a a^b=a×a×...×a×a ab=a×a×...×a×a b b b a a a相乘)
  2. a x × a y a^x×a^y ax×ay
    = a × a × . . . × a \red a\red ×\red a\red×\red.\red.\red.\red×\red a a×a×...×a ( x (x (x a ) a) a) × × × a × a × . . . × a × a \blue a\blue ×\blue a\blue ×\blue .\blue .\blue .\blue ×\blue a\blue ×\blue a a×a×...×a×a ( y (y (y a ) a) a)
    = ( x + y ) (x+y) (x+y) a a a相乘的积
    = a a ax+y
    例如 2 3 ∗ 2 5 = 2 8 2^3*2^5=2^8 2325=28

正文

有了以上的性质,我们来分析这个式子:

b b b为偶数时,

a b = a a^b=a ab=ab/2 ∗ * a a ab/2

很好理解,因为 b / 2 + b / 2 = b b/2+b/2=b b/2+b/2=b

b b b为奇数时呢?肯定不能像偶数那样操作,直接 ÷ 2 ÷2 ÷2

我们思考小学数学的一个概念,所有奇数都可以表示为 2 n + 1 2n+1 2n+1的形式

假设这个奇数为 x x x

x = 2 n + 1 x=2n+1 x=2n+1
2 n = x − 1 2n=x-1 2n=x1
n = ( x − 1 ) / 2 n=(x-1)/2 n=(x1)/2
n = x / 2 − 0.5 n=x/2-0.5 n=x/20.5

我们想一下, x x x是一个奇数,所以 x / 2 x/2 x/2的小数部分必然是 . 5 .5 .5 x / 2 − 0.5 x/2-0.5 x/20.5相当于去掉了 x / 2 x/2 x/2的小数部分,也就是将 x / 2 x/2 x/2向下取整。

所以,奇数 x = ⌊ x / 2 ⌋ + ⌊ x / 2 ⌋ + 1 x=⌊x/2⌋+⌊x/2⌋+1 x=x/2+x/2+1

⌊ x / 2 ⌋ ⌊x/2⌋ x/2表示 x / 2 x/2 x/2的商向下取整。

所以, 对于任意奇数 b b b a b = a a^b=a ab=a⌊b/2⌋ ∗ * a a a⌊b/2⌋ ∗ * a 1 a^1 a1 成立。

例:

2 6 = 2 3 ∗ 2 3 2^6=2^3*2^3 26=2323
2 9 = 2 4 ∗ 2 4 ∗ 2 2^9=2^4*2^4*2 29=24242

这时,我们会惊奇的发现,

a b a^b ab的问题就可以根据 b b b的奇偶性转化:

  1. b b b是奇数,将 a b a^b ab的问题转化成求
    a a a⌊b/2⌋ ∗ * a a a⌊b/2⌋ ∗ * a a a
    2.若 b b b是偶数, 将 a b a^b ab的问题转化成求 a a ab/2 ∗ * a a ab/2

因为 a a ab/2的求解规模一定比求 a b a^b ab小,所以转化是有意义的,减少了运算规模。

有了思路,我们需要考虑一下,用什么算法呢?

我们回到前面,我们是将大任务转化成小任务求解。 a b a^b ab的目标问题可以根据子问题( a a ab/2)求解,对于这类问题,我们通常使用递归算法求解。

如果不会递归算法,可以自己上网查阅相关资料 。


初步代码

#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL F(LL a,LL b) {
    if(b==1) return a; //递归边界 a^1=a
    if(b%2==1) return F(a,b/2)*F(a,b/2)*a;  //指数是奇数
    else return F(a,b/2)*F(a,b/2);  //指数是偶数
} 
int main() {
    LL a,b;
    cin>>a>>b;
    cout<<F(a,b);
}

程序的优化

上面的代码思路是完全正确的,但是程序真正运行起来感觉速度并没有快很多。

我们画一个图来理解我们的程序。

假设我们要求 2 8 2^8 28,我们就要先求 2 4 2^4 24,
想要求 2 4 2^4 24 , 我们就要求 2 2 2^2 22,
想要求 2 2 2^2 22 , 我们就要求 2 1 2^1 21
画成图就是这样的:

1 1 1         2 8 2^8 28
2 2 2      /     \
3 3 3      2 4 2^4 24   ×    2 4 2^4 24
4 4 4     /   \    /    \
5 5 5     2 2 2^2 22 ×  2 2 2^2 22   2 2 2^2 22 ×  2 2 2^2 22
6 6 6   / \  / \  / \  / \
7 7 7   2 1 2^1 21 × 2 1 2^1 21 2 1 2^1 21 × 2 1 2^1 21 2 1 2^1 21 × 2 1 2^1 21 2 1 2^1 21 × 2 1 2^1 21

我们可以发现第 3 3 3 2 4 2^4 24算了两次,而算 2 4 2^4 24的代价是非常大的,我们完全没有必要算两遍 2 4 2^4 24,算一遍,将它的结果记录下来备用就可以了。

修改后的代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL F(LL a,LL b) {
    if(b==1) return a; //递归边界 a^1=a
    int res=F(a,b/2);  //记录答案
    if(b%2==1) return res*res*a;  //指数是奇数
    else return res*res;  //指数是偶数
} 
int main() {
    LL a,b;
    cin>>a>>b;
    cout<<F(a,b);
}

这个程序算 2 8 2^8 28画成图是这样的:

1 1 1         2 8 2^8 28
2 2 2      /     
3 3 3      ( 2 4 ) 2 (2^4)^2 (24)2   
4 4 4     /      
5 5 5     ( 2 2 ) 2 (2^2)^2 (22)2 
6 6 6   /   
7 7 7   ( 2 1 ) 2 (2^1)^2 (21)2

这个算法的时间复杂度是 O ( l o g \red O\red (\red l\red o \red g O(log b ) \red b\red ) b)


您听懂了吗?

写作不易,不喜勿喷,留个赞吧!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值