【数论】快速幂

发布于

一、快速幂的概念

快速幂,就是求某个数 ​a​k 次幂的一种快速方法。直接计算的时间复杂度为 ​O(n),而使用快速幂的时间复杂度仅为 ​O(\log k)


二、基本思路

我们来看一个简单的例子:求 ​a^k \bmod p

思路一:暴力

直接循环求出 ​a^k,时间复杂度为 ​O(k)。当 ​k 较大时,容易超时。因此,我们可以采用快速幂算法。

思路二:快速幂

快速幂的基本思想是反复平方

首先,我们预处理出:

a^{2^{0}}, a^{2^{1}}, a^{2^{2}}, \dots, a^{2^{\log k}}

​\log k 个数。细心的读者可能已经发现,这些数恰好对应 ​k 的二进制展开中每一位的权值。于是,将 ​k 用二进制表示,就可以将 ​a^k 表示为若干上述数的乘积。

示例
​4^5。将 ​5 化为二进制 ​(101)_2,则:

4^5 = 4^{(101)_2} = 4^{2^0} \cdot 4^{2^2}

因为二进制可以表示所有整数,所以我们只需通过 b & 1 判断当前二进制位是否为 1,若是,则乘上对应的底数幂;否则不乘。

但要注意,如何保证每次处理的位权正确呢?
方法是:每次循环都将底数平方,即从 ​a^{2^0} 变为 ​a^{2^1},再变为 ​a^{2^2},以此类推。这样,无论当前位是否为 1,底数都不断平方,从而与二进制位权对齐。

示例代码

typedef long long LL;

LL qmi(int a, int b, int p) {
    int res = 1;
    while (b) {
        if (b & 1) res = (LL)res * a % p;
        b >>= 1;
        a = (LL)a * a % p;
    }
    return res;
}

三、快速幂求逆元

1. 逆元的定义

若整数 ​b​m 互质,且对于任意整数 ​a,若 ​b \mid a,存在整数 ​x 使得:

\frac{a}{b} \equiv a \cdot x \pmod{m}

则称 ​x​b 的模 ​m 乘法逆元,记为 ​b^{-1} \pmod{m}

​b 存在乘法逆元的充要条件是 ​b 与模数 ​m 互质。当模数 ​m 为质数时,​b^{m-2} 即为 ​b 的乘法逆元。

2. 逆元的求法

由逆元定义:

\frac{a}{b} \equiv a \cdot x \pmod{m}

两边同时乘以 ​b

a \equiv a \cdot x \cdot b \pmod{m}

因为 ​b​m 互质,且 ​a 可约,得到最简式:

b \cdot x \equiv 1 \pmod{m}

由费马小定理:当 ​m 为质数 ​p 时,

b^{p-1} \equiv 1 \pmod{p}

即:

b \cdot b^{p-2} \equiv 1 \pmod{p}

​b \cdot x \equiv 1 \pmod{p} 对比,可得:

x = b^{p-2}

因此,​b 的逆元即为 ​b^{p-2},可用快速幂求解。

示例代码

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long LL;

LL qmi(int a, int b, int p) {
    int res = 1;
    while (b) {
        if (b & 1) res = (LL)res * a % p;
        b >>= 1;
        a = (LL)a * a % p;
    }
    return res;
}

int main() {
    int n;
    scanf("%d", &n);
    while (n--) {
        int a, p;
        cin >> a >> p;
        int res = qmi(a, p - 2, p);
        if (a % p != 0) printf("%d\n", res);
        else printf("impossible\n");
    }
    return 0;
}

四、例题讲解

例题:AcWing 1289. 序列的第 ​k 个数

BSNY 在学习等差数列和等比数列,已知前三项时,就可以判断是等差还是等比数列。

现在给你一个整数序列的前三项,这个序列要么是等差,要么是等比,请你求出第 ​k 项的值。如果结果太大,对其取模 200907

输入格式

第一行一个整数 ​T,表示有 ​T 组测试数据。
每组测试数据输入前三项 ​a, b, c,然后输入 ​k

输出格式

对于每组数据,输出第 ​k 项取模 200907 的值。

数据范围

  • ​1 \leq T \leq 100
  • ​1 \leq a \leq b \leq c \leq 10^9
  • ​1 \leq k \leq 10^9

输入样例

2
1 2 3 5
1 2 4 5

输出样例

5
16

解题思路

首先考虑:是否存在前三项既是等差又是等比的情况(除 ​a = b = c 外)?
实际上,除了常数数列,不存在其他情况。证明如下:

设首项为 ​x,若为等比数列,则第二项为 ​xd,第三项为 ​xd^2
若为等差数列,则第二项为 ​x + p,第三项为 ​x + 2p
若同时满足,则有:

\begin{cases} x = x_1 \\ x + p = xd \\ x + 2p = xd^2 \end{cases}

显然,只有常数数列(包括全 0)满足,其他情况无解。

因此,我们只需判断是等差还是等比:

  • 若为等差数列,公差 ​d = b - a,第 ​k 项为:

    a + d \cdot (k - 1)
  • 若为等比数列,公比 ​q = \frac{b}{a},第 ​k 项为:

    a \cdot q^{k-1} = a \cdot \left(\frac{b}{a}\right)^{k-1}

    其中 ​q^{k-1} 可用快速幂计算。


参考代码

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 200907;

int qmi(int a, int k) {
    int res = 1;
    while (k) {
        if (k & 1) res = (LL)res * a % N;
        a = (LL)a * a % N;
        k >>= 1;
    }
    return res;
}

int main() {
    int n;
    cin >> n;
    while (n--) {
        int a, b, c, k;
        cin >> a >> b >> c >> k;
        if (a + c == b * 2) {  // 等价于 c - b == b - a
            cout << (a + (b - a) * (LL)(k - 1)) % N << endl;
        } else {
            cout << (LL)a * qmi(b / a, k - 1) % N << endl;
        }
    }
    return 0;
}
浏览(5)
评论

请登录后发表观点

暂无数据