Algorinote_1_快速幂

看大黑书里提到了幂运算可以通过二分优化,今个用上了.

递归思路

对幂运算进行对分.
在这里插入图片描述

利用递归执行代码很简单.

typedef long long ll
ll pow_V0(ll a , ll n)
{
    if (!n)
        return 1;
    else if (n % 2 == 1)
        return a * pow(a, n-1);
    else {
        ll temp = pow(a, n/2); //不能省去,要让q_pow()只执行一次
        return temp * temp;
    }
}

优化

为了避免temp的出现,我们还可以这样列.
在这里插入图片描述

其不同之处是把平方留在函数实参里面,先平方再计算次方,也就是在函数调用之前就进行平方,自然不存在平方调用两次函数的问题.

留意到 n 为奇数时,此处 a ^ {n-1} 要再回到 n 为偶数的情况,似乎有一点浪费资源,不如:
在这里插入图片描述

ll pow_V1(ll a , ll n)
{
    if (!n)
        return 1;
    else if (n % 2 == 1)
        return a * pow_V1(a * a, n/2);
    else
        return pow_V1(a * a, n/2);
}

与2相关的运算还可以用速度更快的位运算代替:

ll pow_V2(ll a , ll n)
{
    if (!n)
        return 1;
    else if (n & 1)
        return a * pow_V2(a * a, n>>1);
    else
        return pow_V2(a * a, n>>1);
}

错误修改

我们刚刚的第一段 V0 程序中先用函数得到 a 的 n/2 次方,再将其平方,平方时为不重复调用需要一个temp变量。那不如异想天开一下:用 pow(X , 2) 代替平方就不需要额外变量了。

这样明显是舍近求远毫无必要,但我们应当注意到此处暗藏着的一个递归书写的严重错误。

return pow(pow(a, n/2), 2);

当 n 为 2 时,pow() 中将会同样调用一个以 2 为第二参数的 pow() (上式),这意味着得到 pow(X , 2) 需要它本身的结果。这样程序会产生一个无限循环直至崩溃。

使用递归时要注意递归链靠近末端处,是否会这样原地转圈。

循环思路

递归确实好写,但栈空间占用大,我们尝试将其改写成循环。

ll pow_V3(ll a , ll n)
{
    ll ret = 1;
    while (n)
    {
        if (n & 1)
            ret *= a;
        a *= a; //将底数平方
        n >>= 1;
    }

    return ret;
}

我们把次方数 n 用二进制表示出来(以5为例):
a 5 = a 101 b = ( 1 ∗ a 1 b ) ∗ ( 0 ∗ a 10 b ) ∗ ( 1 ∗ a 100 b ) = a 1 ∗ a 4 a ^ {5} = a ^ {101b} \\ = (1*a^{1b})*(0*a^{10b})*(1*a^{100b}) \\ = a^{1}*a^{4} a5=a101b=(1a1b)(0a10b)(1a100b)=a1a4
从右到左每处理一位二进制,将底数 a 平方,这样处理下一个二进制位时(二进制位的 “1” 代表的数翻倍),如果该位是 “1”,ret 相应也会乘上的也是 次方数翻倍 的 a。

超出范围

求幂运算中很可能会碰到结果及其巨大的情况,导致数据溢出。

如计算2的64次方,即使是long long类型也顶不住,得到的结果是0。(次方数再继续增加也只是计算 0 ^ {n} )

我们需要对计算结果进行取模,且只能在每一次运算后都取一次模

ll pow_V4(ll a , ll n)
{
    ll ret = 1;
    while (n)
    {
        if (n & 1)
            ret *= a % MOD;
        a *= a % MOD;
        n >>= 1;
    }

    return ret;
}

如果是针对类型大小的范围取模,位运算也可以用来取模(屏蔽二进制高位)

ret *= a & 4294967296 -1
ret *= a & 0X100000000 -1
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值