快速幂

标准幂

在编程中我们往往会遇到幂的运算,正常情况下许多初学者会用以下两者方式计算(ab

1.使用for循坏进行幂运算

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    ll a, b, ans = 1;
    cin >> a >> b;
    for (int i = 1; i <= b; ++i) {
        ans *= a;
    }
    cout << ans;
    return 0;
}

2.直接调用pow()函数进行幂运算

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    ll a, b;
    cin >> a >> b;
    cout << pow(a , b);
    return 0;
}

但实际情况下,在计算大数据的题目时,由于两者的时间复杂度都为O(n),我们一般都是会T的(TLE,即运行超时),这时我们应当考虑快速幂了

快速幂

1.原理

我们先假设需要计算 211,这时我们可以将其分解为 2(8+2+1) = 28 * 22 * 21。这时它的时间复杂度就会大大缩减成O(log2n)

(1)幂次与二进制的关系

其中 21,22,24,28…… 的幂次都是2的倍数,所有幂2i都是倍乘关系,可以进行逐级递推

(2)幂次用二进制分解

学过二进制的都知道,11的二进制为1011,二进制换算成十进制时的展开式为 28 + 22 + 21,问题迎刃而解

(3)跳过没有的幂次

24 如何处理?只需做一个判断,这时候我们可以使用二进制的位运算实现,即
b & 1,取 b 的二进制的最后一位数,判断这一位是否需要跳过
b >>= 1,把 b 右移一位,此目的为把刚处理过的 b 的二进制的最后一位去掉

(4)代码实现

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    ll a, b, ans = 1;
    cin >> a >> b;
    while (b) {
        if (b & 1) ans *= a;
        a *= a;
        b >>= 1;
    }
    cout << ans;
    return 0;
}

当然,实际情况下幂运算结果往往是一个大数,题目一般会要求取模后在输出。不过不能对结果直接进行取模,因为结果可能早已超出 long long 的数据范围。我们需要用到取模的性质 ab % mod n = (a % n)b mod n,则代码可为

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

int main() {
    ll a, b, ans = 1, n = 1000000007;
    cin >> a >> b;
    a %= n;
    while (b) {
        if (b & 1) ans = (ans * a) % n;
        a = (a * a) % n;
        b >>= 1;
    }
    cout << ans;
    return 0;
}

不过你要注意,写代码时应该要让主代码简便,所以我们需要把快速幂变为一个函数,使用时调用即可,以下为上述两种代码的函数

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;

ll fastPow1(ll a, ll b) {
    ll ans = 1;
    while (b) {
        if (b & 1) ans *= a;
        a *= a;
        b >>= 1;
    }
    return ans;
}
ll fastPow2(ll a, ll b, ll n) {
    ll ans = 1;
    a %= n;
    while (b) {
        if (b & 1) ans = (ans * a) %n;
        a = (a * a) % n;
        b >>= 1;
    }
    return ans;
}
int main() {
    ll a, b, n = 1000000007;
    cin >> a >> b;
    cout << fastPow1(a, b) << ' ' << fastPow2(a, b, n);
    return 0;
}

(5)代码理解

以 a = 2,b = 11 为例

b(原:11) a(原:2) ans(原:1)
11 4 2
5 16 8
2 256 8
1 65536 2048

表格第一行:

  • b = 11 时,即 while(11) 为true;
  • 11 的二进制为 1011,1011 位于上 1,可得 1(即 1011 位于 0001),即 if(1) 为 true,ans *= a 执行,ans 为2;
  • a *= a 执行,a = 4;
  • b >= 1 执行,b = 5(二进制为 101);

表格第二行:

  • b = 5 时,即 while(5) 为true;
  • 5 的二进制为 101,101 位于上 1,可得 1,即 if(1) 为 true,ans *= a 执行,ans 为8;
  • a *= a 执行,a = 16;
  • b >= 1 执行,b = 2(二进制为 10);

表格第三行:

  • b = 2 时,即 while(2) 为true;
  • 2 的二进制为 10,10 位于上 1,可得 0,即 if(0) 为 false,ans *= a 不执行;
  • a *= a 执行,a = 256;
  • b >= 1 执行,b = 1(二进制为 1);

表格第四行:

  • b = 1 时,即 while(1) 为true;
  • 1 的二进制为 1,1 位于上 1,可得 1,即 if(1) 为 false,ans *= a 执行,ans 为2048;
  • a *= a 执行,a = 65536,不过已经没什么用了;
  • b >= 1 执行,b = 0(二进制为 0),下次循坏 while(0) 为 false,程序结束;

由此可知,在处理没有的幂次时,直接跳过 ans *= a 这一条语句,直接执行 a *= a 这一语句,这样就能实现 a2 直接变为 a8
而 b >= 1 这一语句则能完美实现把处理过的位数删去,代码就这样水灵灵的出来了

(6)注意

  1. 如果遇到特别大的数(a 特别大,超出 long long 的范围),快速幂无法计算,只能用高精度了,当然没也可以用 Python,只要你不怕T;
  2. 快速幂使用任何求幂,除了特别大的数;
  3. 写代码时需要谨慎,用快速幂的函数替代 pow() 函数;

我只是分享了一下学习心得,共给大家查阅,如有不妥处,欢迎大家指正꒰ᐢ⸝⸝•༝•⸝⸝ᐢ꒱​​
posted @ 2024-10-21 13:05  ZGonce819  阅读(49)  评论(0编辑  收藏  举报