C++快速幂完整实战讲解

C++快速幂完整实战讲解

1. 背景与意义

指数运算(a^n)是数学与计算机科学中最常见的操作之一。直观的计算方法是把基数 an 次,但是当 n 很大(如数十亿、上亿甚至 10<sup>18</sup>)时,线性循环的时间复杂度 O(n) 将无法接受。为此,快速幂 (又名"左移右乘"或 "指数平方法")提供了一种将时间复杂度降低到 O(log n) 的技术。

在多数算法竞赛、密码学、数论与线性代数等领域,快速幂往往是必备技术。例如:

  • 计算模幂 (pow_mod) 用于朴素加密/解密、RSA 中的大数运算;
  • 计算阶乘模幂用来实现 C(n, k) 的快速求法;
  • 解决 a^b % m 的竞赛题目;
  • 计算矩阵平方幂实现快速求斐波那契数、线性递推等。

因为其运算简单、代码可读性高、且兼容几乎所有编程语言,快速幂已成为电脑学者的标配技巧。


2. 快速幂的核心思想

快速幂基于以下拆解公式:

an={an/2×an/2如果 n 是偶数an−1×a如果 n 是奇数an={an/2×an/2an−1×a​如果 n 是偶数如果 n 是奇数​

可进一步写成:

an=an/2×an/2若 n 为偶(a(n−1)/2×a(n−1)/2)×a若 n 为奇an=an/2×an/2(a(n−1)/2×a(n−1)/2)×a​若 n 为偶若 n 为奇​

这个拆分利用了 二进制 的特性:从高位到低位逐步处理指数二进制位。具体实现有两大变体:

  1. 递归实现:直接按上面公式递归求解;
  2. 迭代实现 :利用指数的最低有效位,累积结果,同时把指数右移一位(n >>= 1),把基数平方。

两种实现都在 O(log n) 的时间复杂度内完成指数运算。


3. 递归实现

3.1 基本代码

cpp 复制代码
long long powerRec(long long a, long long n) {
    if (n == 0) return 1;          // 任何数的 0 次方为 1
    long long half = powerRec(a, n >> 1);
    if (n & 1)   // 奇数
        return half * half * a;
    else         // 偶数
        return half * half;
}

3.2 关键点解读

  • n == 0 为递归终止条件。
  • n >> 1 相当于 n / 2,位运算比除法快。
  • n & 1 判断 n 是否为奇数。
  • 递归深度最多 ⌊log₂ n⌋ + 1,对 n < 10^18 的情况,只需不到 64 层递归。这在大多数现代编译器下完全安全(递归深度通常 1e5 或更高)。

3.3 递归背后的数值问题

  • 整数溢出half * half * a 可能产生 64 位溢出。若仅需要在不取模的情况下求伪值,使用 __int128long double 可缓解;若已知需要取模,可将 halfa 先模,以保证中间结果不大。
  • 无负指数 :上面代码仅适用于 n ≥ 0。若想支持负指数,需要使用浮点数或将题目转成求分数的形式。

4. 迭代实现

迭代实现是快速幂中最常用的形式,代码更简洁,递归带来的函数调用开销也消失。

cpp 复制代码
long long powerIter(long long a, long long n) {
    long long result = 1;
    while (n > 0) {
        if (n & 1)       // 当前最低位为 1
            result *= a; // 乘以对应的基数
        a *= a;          // 基数平方
        n >>= 1;         // 移除最低位
    }
    return result;
}

4.1 迭代不需要递归

  • 空间复杂度:常量 O(1)。
  • 时间复杂度 :同递归,为 O(log n)
  • 可读性:大多数程序员更倾向于迭代版本,易于在代码审查时定位错误。

4.2 迭代与递归的比较

特点 递归 迭代
空间 O(log n)(递归栈) O(1)
性能 额外递归函数调用 纯循环
可读性 直观,符合公式 简洁
安全性 递归深度偶尔超限 无关

在极端大数(如 n > 10^9)的情况下,递归方式完全无问题;但如果你在嵌入式或最小化内存场景,迭代更稳妥。


5. 取模实现(模幂)

当我们需要求 a^n mod m(特别是 m 为 10<sup>9</sup>+7 等 32 位或 64 位素数)时, 直接做 half * half 计算会溢出。我们可以在每一步都做模运算,保证中间值不会太大。

cpp 复制代码
long long modpow(long long a, long long n, long long mod) {
    long long result = 1 % mod;
    a %= mod;
    while (n > 0) {
        if (n & 1)
            result = (__int128)result * a % mod; // __int128 防止 64 位乘溢出
        a = (__int128)a * a % mod;
        n >>= 1;
    }
    return result;
}

5.1 关键技巧

  1. 使用 __int128 :当 amod 都接近 10<sup>18</sup> 时,a * a 会溢出 64 位。__int128(若编译器支持)可以安全处理 128 位整数。
  2. 按位取模a %= mod 先对基数做模,随后所有乘法保持在 [0, mod) 范围内,避免乘溢出。
  3. 取模的顺序:一定先做乘,再做取模,而不是先取模后再乘。

5.2 典型应用场景

需求 模幂实现
1. 计算 a^b mod M(如 M = 1e9+7) modpow(a, b, M)
2. 计算组合 C(n, k) mod M 用费马小定理,C(n, k) = fact[n] * invfact[k] * invfact[n-k] mod M,其中 invfactmodpow(fact, M-2, M) 的结果
3. 计算斐波那契数 F(n) mod M 用矩阵快速幂,矩阵元素同样做模运算

6. 栈与缓存:优化可读性与性能

6.1 将模幂抽成函数模板

在多语言项目中,模幂经常被多处调用。使用模板可以让编译器生成类型安全的代码。

cpp 复制代码
template<typename T, typename U>
T modpow(T a, U n, T mod) {
    T res = 1 % mod, base = a % mod;
    while (n > 0) {
        if (n & 1) res = (__int128)res * base % mod;
        base = (__int128)base * base % mod;
        n >>= 1;
    }
    return res;
}
  • 这里我们允许 n 为更宽的整数类型(unsigned long long),但求值时会自动转换。
  • 若使用 uint64_t__int128 需要 unsigned __int128

6.2 递归式的合并

如果需要在递归中间做模,比如:

cpp 复制代码
T modpowRec(T a, U n, T mod) {
    if (n == 0) return 1 % mod;
    T half = modpowRec(a, n >> 1, mod);
    if (n & 1) return (__int128)half * half % mod * a % mod;
    else return (__int128)half * half % mod;
}

注意 half 先做过模,防止溢出。


7. 负指数与浮点数

7.1 负整数指数

在整数域上,负指数意味着求 a 的求逆(如果 a 可逆)。若 mod 为素数,可以借助费马小定理求逆:

a−n≡(a−1)n(modm)a−n≡(a−1)n(modm)

其中 a^{-1} = a^{m-2} \bmod m.

cpp 复制代码
long long modpowNeg(long long a, long long n, long long mod) {
    // 先取 a 的逆
    long long inv_a = modpow(a, mod - 2, mod); // precondition: mod is prime
    return modpow(inv_a, n, mod);
}

7.2 浮点指数

如果题目要求 x^n(其中 x 为浮点数,不取模),可以使用 std::pow。但其实快速幂也能配合浮点数使用:

cpp 复制代码
double powFloat(double a, long long n) {
    double res = 1.0;
    while (n > 0) {
        if (n & 1) res *= a;
        a *= a;
        n >>= 1;
    }
    return res;
}

std::pow 相比,它在说明"快速幂"时更显直观。虽然 sqrtlog 等更高级实现会被优化,但对于一般竞赛来说差别不大。


8. 常见错误与陷阱

  1. 忘记取模 :若没有 MOD,a * a 在大数时会溢出。
  2. 使用 int 而不是 long long :尤其在 OJ 的 INT_MAX 之后测试数据会导致错误。
  3. 链式乘法result = result * a; 先乘后取模,但若 result 过大会溢出。
  4. 负数模 :在 C++ 中 -1 % mod 结果为 -1,而我们通常需要正模结果。可在取模后加 mod% mod
  5. 复合模(多模):若需要满足多个模,使用 CRT(Chinese Remainder Theorem)或将每一步都做模。
  6. n 的二进制时使用 unsigned long long:避免因为负数右移产生负号。

9. 复杂度分析

  • 时间
    • 递归 & 迭代:Θ(log₂ n)
    • n = 10^18,循环次数约为 60。
    • 乘法 __int128 速度稍慢,但可接受。
  • 空间
    • 迭代:O(1)
    • 递归:O(log n)(约 60 层栈)。
  • mod 的限制 :如果 mod 远小于 2<sup>63</sup>, __int128 能保证中间结果不溢。

10. 进阶应用:矩阵快速幂

矩阵幂同样可以使用快速幂思想。设 An × n 矩阵,则:

An=An/2×An/2若 n 为偶(A(n−1)/2×A(n−1)/2)×A若 n 为奇An=An/2×An/2(A(n−1)/2×A(n−1)/2)×A​若 n 为偶若 n 为奇​

实现通常采用自定义 Matrix 结构,并提供 multiply(按模运算)和 power(迭代或递归)。

cpp 复制代码
struct Matrix {
    static const int N = 3; // 例:3x3
    long long a[N][N];
    Matrix identity() {
        Matrix I;
        for (int i = 0; i < N; ++i)
            for (int j = 0; j < N; ++j)
                I.a[i][j] = (i == j);
        return I;
    }
    Matrix operator*(const Matrix& other) const {
        Matrix res;
        for (int i = 0; i < N; ++i)
            for (int j = 0; j < N; ++j) {
                __int128 sum = 0;
                for (int k = 0; k < N; ++k)
                    sum += (__int128)a[i][k] * other.a[k][j];
                res.a[i][j] = (long long)(sum % MOD);
            }
        return res;
    }
    Matrix pow(long long exp) const {
        Matrix result = identity();
        Matrix base = *this;
        while (exp > 0) {
            if (exp & 1) result = result * base;
            base = base * base;
            exp >>= 1;
        }
        return result;
    }
};

10.1 用法示例:斐波那契数

斐波那契数可表示为:

(Fk+1Fk)=(1110)k(F1F0)(Fk+1​Fk​​)=(11​10​)k(F1​F0​​)

所以 F_n = (M^n)[0][0]

cpp 复制代码
long long fib(long long n, long long mod) {
    Matrix M;
    M.a[0][0] = 1; M.a[0][1] = 1;
    M.a[1][0] = 1; M.a[1][1] = 0;
    return M.pow(n).a[0][0];
}

双倍出-斐波那契(F_{2k} 作为 F_k * (2*F_{k+1} - F_k)) 亦可利用快速幂来实现。


11. 预先计算与组合

11.1 取模逆元素

在组合数求模时,需要用到阶乘的逆元。根据费马小定理:

x−1≡xm−2(modm)(m 为素数)x−1≡xm−2(modm)(m 为素数)

所以我们可以预先计算 factinvfact

cpp 复制代码
const int MAXN = 1e6;
long long fact[MAXN], invfact[MAXN];
void precompute() {
    fact[0] = 1;
    for (int i = 1; i < MAXN; ++i)
        fact[i] = fact[i-1] * i % MOD;
    invfact[MAXN-1] = modpow(fact[MAXN-1], MOD-2, MOD);
    for (int i = MAXN-2; i >= 0; --i)
        invfact[i] = invfact[i+1] * (i+1) % MOD;
}
long long C(int n, int k) {
    if (k < 0 || k > n) return 0;
    return fact[n] * invfact[k] % MOD * invfact[n-k] % MOD;
}

此方法在 O(1) 时间即可求任意 C(n, k) (n < 1e6)

11.2 相关变形

  • 求组合数C(a + b, a)C(n, k)
  • 指数组合nCr = (n! / r! / (n-r)!) mod M
  • 圆盘堆叠 :常出现的 C(n + k - 1, k)C(n, k),同样使用预处理。

12. 负数模、粘模

有时模数 M 不是素数,或者是 int 负数。C++ 的取模运算对负数的行为为"保持符号",导致 -1 % 100 得到 -1,而我们通常想得到 99。处理办法:

cpp 复制代码
long long modFix(long long x, long long mod) {
    return (x % mod + mod) % mod;
}

或者在每次取模前做 ((x % mod) + mod) % mod


13. 结合多模 & CRT

在有些题目中,需要 a^n mod m1a^n mod m2 等多组模运算结果合并为一个大数。此时可使用中国剩余定理(CRT):

  1. 先用快速幂得到各模下的结果 r1, r2, ..., rk
  2. 通过 CRT 将六个结果合并为一组:
cpp 复制代码
long long CRT(vector<long long> residues, vector<long long> mods) {
    long long x = 0, M = 1;
    for (size_t i = 0; i < residues.size(); ++i) {
        long long m = mods[i];
        long long ri = residues[i];
        long long t = ((__int128)(ri - x) * modpow(M, m-2, m)) % m;
        x += M * t;
        M *= m;
    }
    return x;
}

M 必须在 __int128 范围内,否则会溢出。


14. 取值范围与边界考虑

场景 取值范围 推荐实现
int < 2<sup>31</sup> < 2<sup>31</sup> 迭代,使用 long long 进行乘法
long long < 2<sup>63</sup> < 2<sup>63</sup> __int128 乘法
需要正模 > 2<sup>63</sup> __int128 + mod 取模
取模素数 1e9+7 1e9+7 long long 足够,long long * long long 生产64 位,__int128 仍是安全最佳
非素数 mod 任何 同自行限制,中间乘可以到 mod^2,需 __int128

选择 __int128 是最保险的做法;若编译器不支持,可拆分成 64 位乘法,或使用第三方大整数库 (如 Boost.Multiprecision::cpp_int)。但大多竞赛的编译器(GCC 9+)都支持 __int128


15. 代码集成与范例

以下给出一个完整、可直接跑通的 CPP 示例,涵盖快速幂、取模、矩阵幂、组合数求模、负指数处理与 CRT,并在 main 函数里演示多种用法。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const long long MOD = 1000000007LL;

/* ---------- Fast Modular Power ---------- */
long long modpow(long long a, long long n, long long mod = MOD) {
    long long res = 1 % mod;
    a %= mod;
    while (n > 0) {
        if (n & 1)
            res = (__int128)res * a % mod;
        a = (__int128)a * a % mod;
        n >>= 1;
    }
    return res;
}

/* ---------- Negatives & Positive Mod & CRT helper ---------- */
long long mod_fix(long long x, long long mod) {
    return (x % mod + mod) % mod;
}
long long crt(const vector<long long>& rs, const vector<long long>& ms) {
    long long x = 0, M = 1;
    for (size_t i = 0; i < rs.size(); ++i) {
        long long m = ms[i];
        long long r = rs[i];
        long long t = (__int128)(r - x) * modpow(M, m-2, m) % m;
        x = (x + M * t) % (M * m);
        M *= m;
    }
    return x;
}

/* ---------- Matrix 3x3 exemplar ---------- */
struct Matrix {
    static const int SZ = 3;
    long long a[SZ][SZ];
    Matrix() { memset(a, 0, sizeof(a)); }
    static Matrix identity() {
        Matrix I;
        for (int i = 0; i < SZ; ++i) I.a[i][i] = 1;
        return I;
    }
    Matrix operator*(const Matrix& o) const {
        Matrix r; long long tmp;
        for (int i = 0; i < SZ; ++i)
            for (int j = 0; j < SZ; ++j) {
                __int128 sum = 0;
                for (int k = 0; k < SZ; ++k)
                    sum += (__int128)a[i][k] * o.a[k][j];
                r.a[i][j] = (long long)(sum % MOD);
            }
        return r;
    }
    Matrix pow(long long e) const {
        Matrix r = identity(), b = *this;
        while (e > 0) {
            if (e & 1) r = r * b;
            b = b * b;
            e >>= 1;
        }
        return r;
    }
};
Matrix make_stirling() {          // 3x3 example matrix
    Matrix M;
    M.a[0][0] = 0; M.a[0][1] = 1; M.a[0][2] = 0;
    M.a[1][0] = 0; M.a[1][1] = 0; M.a[1][2] = 1;
    M.a[2][0] = 1; M.a[2][1] = 1; M.a[2][2] = 0;
    return M;
}

/* ---------- Precomputation for nCr ---------- */
const int LIM = 2000000;
long long fact[LIM+1], invfact[LIM+1];
void precompute_fact() {
    fact[0] = 1;
    for (int i = 1; i <= LIM; ++i) fact[i] = fact[i-1] * i % MOD;
    invfact[LIM] = modpow(fact[LIM], MOD-2);
    for (int i = LIM; i > 0; --i) invfact[i-1] = invfact[i] * i % MOD;
}
inline long long C(int n, int k) {
    if (k < 0 || k > n) return 0;
    return fact[n] * invfact[k] % MOD * invfact[n-k] % MOD;
}

/* ---------- Main demo ---------- */
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    /* ① 快速幂常规用法 */
    long long a = 1234567890123LL;
    long long n = 1234567890LL;
    cout << "a^n mod MOD = " << modpow(a, n) << '\n';

    /* ② 负指数的计算(模数必须是素数) */
    long long inv_a = modpow(a % MOD, MOD-2);    // a^-1
    long long pos_n = 100;                       // a^100
    long long neg_n = modpow(inv_a, 100);        // a^-100 mod MOD
    cout << "a^-100 mod MOD = " << neg_n << '\n';

    /* ③ 矩阵幂支持:S(-1)^n(示例) */
    Matrix M = make_stirling();
    Matrix Mn = M.pow(10);
    cout << "M^10[2][2] = " << Mn.a[2][2] << '\n';

    /* ④ 预处理阶乘、逆阶乘,快速组合数 */
    precompute_fact();
    cout << "C(10,3) = " << C(10,3) << '\n';   // 120
    cout << "C(2000000,1000) = " << C(2000000,1000) << '\n';

    /* ⑤ 正负模以及 CRT 合并示例 */
    long long r1 = modpow(2, 100, 3);   // 2^100 mod 3
    long long r2 = modpow(2, 100, 5);   // 2^100 mod 5
    vector<long long> rs = {r1, r2};
    vector<long long> ms = {3, 5};
    cout << "CRT(2^100 mod 15) = " << crt(rs, ms) << '\n';

    return 0;
}

上述代码已通过编译器(GCC 12.2 / Clang 14)测试,涵盖了常见场景。若您在 OJ / 代码库中使用,可直接拷贝。


16. 性能微调与实战注意

  1. 循环展开 :若 n 较大,可以把 while (n) 循环拆成十几条 if (n & 1) ...,减少分支开销。
  2. 关闭 I/O 同步 :在大 I/O 场景下,使用 ios::sync_with_stdio(false); cin.tie(nullptr);
  3. 编译优化 :开启 -O3-march=native,保证编译器把 __int128 乘法优化为 mul + sal/shl
  4. 使用模板 :若需要对多种整数(int32, int64, __int128)做快速幂,可写成模板。
  5. 避免不必要的 % mod :在顶层循环里,如果 a 已经小于 mod,不必再次取模。
  6. 实验不同实现 :若时间吃紧,可用递归尾递归保存inline + register)或将递归改写成 for ( ; n; n = n >> 1) 的方式。
  7. 剖析特殊情况 :特别是 mod == 1 时,无论何种指数,结果都是 0;判断 mod == 1 可以直接返回 0,避免 modpow 里取模操作导致 0/0。

17. 结语

快速幂是解决指数问题的底层构件 。一次完整、细致的 C++ 实现不只是一个单纯的"算法实现",更是对 算术运算、位运算、循环、递归、模板、整数溢出、取模、矩阵、组合、负数处理、CRT 等多方面知识的综合体现。

无论是 算法竞赛简单题 (如 3^1000 % 1e9+7)、中等难度题 (矩阵斐波那契)、高阶题 (多模合并 + 线性递推)还是 科研项目(大数据模运算),掌握快速幂的实现原则与细节都是不可或缺的。

只要能写出一个 完整、没有黑洞、可运行、并且自带测试 的实现,就意味着你已具备高质量代码 的基础与能力。祝你在后续的算法旅程中不断发掘更多应用场景,再次感谢阅读!

相关推荐
Mr_pyx1 小时前
【LeetHOT100】随机链表的复制——Java多解法详解
算法·深度优先
AI周红伟1 小时前
周红伟:GPT-Image-2深度解析:从技术原理到实战教程,为什么它能让整个AI圈炸锅?
人工智能·gpt·深度学习·机器学习·语言模型·openclaw
AIFarmer2 小时前
【无标题】
开发语言·c++·算法
AGV算法笔记2 小时前
CVPR 2025 最新感知算法解读:GaussianLSS 如何用 Gaussian Splatting 重构 BEV 表示?
算法·重构·自动驾驶·3d视觉·感知算法·多视角视觉
Uopiasd1234oo3 小时前
上下文引导模块改进YOLOv26局部与全局特征融合能力双重提升
深度学习·yolo·机器学习
哥布林学者3 小时前
深度学习进阶(十四)ConvNeXt
机器学习·ai
勤劳的进取家3 小时前
数据链路层基础
网络·学习·算法
Advancer-3 小时前
第二次蓝桥杯总结(上)
java·算法·职场和发展·蓝桥杯
ん贤4 小时前
加密算法(对称、非对称、哈希、签名...)
算法·哈希算法