记录Acwing 3625. 幂次方

博客介绍了快速幂算法,它能以低时间复杂度计算乘方,且在后续很多算法中会用到。详细讲解了递归快速幂的思路、代码及取模处理,还提到数据溢出时使用 long long 类型和 const 变量的用法。此外,介绍了非递归快速幂,通过二进制拆分实现,虽理解稍复杂,但与递归思路相近。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

快速幂

递归快速幂

typedef long long ll;数据溢出时使用

非递归快速幂


 

快速幂

快速幂Exponentiation by squaring,平方求幂)是一种简单而有效的小算法,它可以以gif.latex?O%20%28log%20n%29的时间复杂度计算乘方。快速幂不仅本身非常常见,而且后续很多算法也都会用到快速幂。

递归快速幂

刚刚我们用到的,无非是一个二分的思路。我们很自然地可以得到一个递归方程:

e44f8376bec346d8ae4f418237f356fd.png

计算a的n次方,如果n是偶数(不为0),那么就先计算a的n/2次方,然后平方;如果n是奇数,那么就先计算a的n-1次方,再乘上a;递归出口是a的0次方为1

递归快速幂的思路非常自然,代码也很简单(直接把递归方程翻译成代码即可):

//递归快速幂
int qpow(int a, int n)
{
    if (n == 0)
        return 1;
    else if (n % 2 == 1)
        return qpow(a, n - 1) * a;
    else
    {
        int temp = qpow(a, n / 2);
        return temp * temp;
    }
}

注意,这个temp变量是必要的,因为如果不把gif.latex?a%5E%7B_%7B2%7D%5E%7Bn%7D%7D记录下来,直接写成qpow(a, n /2)*qpow(a, n /2),那会计算两次gif.latex?a%5E%7B_%7B2%7D%5E%7Bn%7D%7D,整个算法就退化为了  gif.latex?O%28n%29

在实际问题中,题目常常会要求对一个大素数取模,这是因为计算结果可能会非常巨大,但是在这里考察高精度又没有必要。这时我们的快速幂也应当进行取模,此时应当注意,原则是步步取模,如果MOD较大,还应当开long long

 

typedef long long ll;数据溢出时使用

数据多大要用long long呢?因为int是四个字节32位,表示的数的范围是-2147483648 ~ 2147483647[-2^31 ~ 2^31-1],2后面是有9位数的,也可以简单记忆为他可以表示10的9次方的数,再大一次方就不行了,有些时候你自己都不知道题目的数据超过了int的范围,当错误有时候不知道怎么解决,就是拿不了满分,那就该怀疑数据范围了,可以尝试把int换为 long long。

//递归快速幂(对大素数取模)
#define MOD 1000000007
typedef long long ll;
ll qpow(ll a, ll n)
{
    if (n == 0)
        return 1;
    else if (n % 2 == 1)
        return qpow(a, n - 1) * a % MOD;
    else
    {
        ll temp = qpow(a, n / 2) % MOD;
        return temp * temp % MOD;
    }
}

有时候我们希望定义这样一种变量,它的值不能被改变,在整个作用域中都保持固定。为了满足这一要求,可以使用const关键字对变量加以限定.如 const int MOD=233333

我们经常将 const 变量称为常量(Constant)。创建常量的格式通常为:

const type name = value;

const 和 type 都是用来修饰变量的,它们的位置可以互换,也就是将 type 放在 const 前面:

type const name = value;

但我们通常采用第一种方式,不采用第二种方式。另外建议将常量名的首字母大写,以提醒程序员这是个常量。

 

const用法详解:C语言const的用法详解,C语言常量定义详解 (biancheng.net)

大家知道,递归虽然简洁,但会产生额外的空间开销。我们可以把递归改写为循环,来避免对栈空间的大量占用,也就是非递归快速幂

非递归快速幂

我们换一个角度来引入非递归的快速幂。还是7的10次方,但这次,我们把10写成二进制的形式,也就是 gif.latex?%281010%29_%7B2%7D

现在我们要计算 gif.latex?7%5E%7B%5E%7B%281010%29_2%7D%7D,可以怎么做?我们很自然地想到可以把它拆分为  gif.latex?7%5E%7B%281000%29_2%7D *gif.latex?7%5E%7B%5E%7B%2810%29_2%7D%7D实际上,对于任意的整数,我们都可以把它拆成若干个 gif.latex?7%5E%7B_%7B%28100...%29_2%7D%7D 的形式相乘。而这些,恰好就是gif.latex?7%5E%7B_%7B1%7D%7D  、gif.latex?7%5E%7B_%7B2%7D%7Dgif.latex?7%5E%7B_%7B4%7D%7D……我们只需不断把底数平方就可以算出它们。

我们先看代码,再来仔细推敲这个过程:

//非递归快速幂
int qpow(int a, int n){
    int ans = 1;
    while(n){
        if(n&1)        //如果n的当前末位为1
            ans *= a;  //ans乘上当前的a
        a *= a;        //a自乘
        n >>= 1;       //n往右移一位
    }
    return ans;
}

最初ans为1,然后我们一位一位算:

1010的最后一位是0,所以a^1这一位不要。然后1010变为101,a变为a^2。

101的最后一位是1,所以a^2这一位是需要的,乘入ans。101变为10,a再自乘。

10的最后一位是0,跳过,右移,自乘。

然后1的最后一位是1,ans再乘上a^8。循环结束,返回结果。

ae65552e892967825aefb0405285db36.png

这里的位运算符,>>是右移,表示把二进制数往右移一位,相当于/2;&是按位与,&1可以理解为取出二进制数的最后一位,相当于%2==1。这么一等价,是不是看出了递归和非递归的快速幂的关系了?虽然非递归快速幂因为牵扯到二进制理解起来稍微复杂一点,但基本思路其实和递归快速幂没有太大的出入。

关于快速幂 修改自:算法学习笔记(4):快速幂 - 知乎 (zhihu.com)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值