NeFut Logo NeFut
EN 管理员登录

深入理解扩展欧拉定理及其在大数降幂中的应用

发布于:2026-05-27 09:17 最后更新:2026-06-06 13:04
#algorithm #optimization #Math

核心逻辑与数学原理

1. 欧拉函数的数学性质

欧拉函数 $\\varphi(N)$ 表示满足 $1 \le i \le N$ 且 $\gcd(i, N) = 1$ 的整数 $i$ 的个数。除线性筛法递推外,它还具备以下数论核心性质:

$$\sum_{d \mid N} \varphi(d) = N$$

$$S = \frac{N \cdot \varphi(N)}{2} \quad (N > 1)$$

2. 欧拉定理(Euler's Theorem)

若正整数 $a$ 与 $m$ 互质(即 $\gcd(a, m) = 1$),则有:

$$a^{\varphi(m)} \equiv 1 \pmod m$$

当 $m$ 为质数时,$\varphi(m) = m - 1$,定理退化为费马小定理:$a^{m-1} \equiv 1 \pmod m$。

3. 扩展欧拉定理(大数降幂公式)

在 NOIP 竞赛中,经常面对求 $a^b \bmod m$ 的场景,其中底数 $a$ 和模数 $m$ 在正常范围内,但指数 $b$ 极大(例如 $b \le 10^{2000000}$,以字符串形式给出)。由于不能直接对指数进行 $b \bmod m$ 操作(这是初学者最常犯的数学常识错误),必须使用扩展欧拉定理进行降幂拦截。

该定理不要求 $a$ 与 $m$ 互质,具有普适性: $$a^b \equiv \begin{cases} a^{b \bmod \varphi(m)} \pmod m & \gcd(a, m) = 1 \ a^b \pmod m & \gcd(a, m) \neq 1 \text{ 且 } b < \varphi(m) \ a^{(b \bmod \varphi(m)) + \varphi(m)} \pmod m & \gcd(a, m) \neq 1 \text{ 且 } b \ge \varphi(m) \end{cases}$$


状态设计与算法推导

单个大数降幂的读取状态设计

对于超长指数 $b$,我们无法将其存入任何原生整型。必须采用单字符流读入(快读变式)进行动态降幂。
在边读边转的过程中,我们需要维护两层状态:

  1. 数值维:当前的指数模 $\varphi(m)$ 的值。
  2. 状态维:当前的真实指数值是否已经超越了 $\varphi(m)$。

定义一个布尔标记 over_limit。设当前已读入的数字构成的数值为 $b'$,读入下一个数字 $d$ 时:

$$b_{\text{new}}' = b' \cdot 10 + d$$

若在某一时刻 $b_{\text{new}}' \ge \varphi(m)$,则令 over_limit = true,并强制执行:

$$b_{\text{new}}' = (b' \cdot 10 + d) \bmod \varphi(m)$$

最终,若 over_limittrue,则最终带入快速幂的指数为 $b' + \varphi(m)$;否则直接为 $b'$。由此实现了将 $O(10^{2000000})$ 规模的指数瞬间压缩至 $O(M)$ 级别。


C++ 标准源码

#include <iostream>
#include <string>

// 单点求解欧拉函数 phi(m),时间复杂度 O(\sqrt{m})
long long get_phi(long long m) {
    long long ans = m;
    long long temp = m;
    for (long long i = 2; i * i <= temp; ++i) {
        if (temp % i == 0) {
            ans = ans / i * (i - 1); // 先除后乘,杜绝截断与溢出
            while (temp % i == 0) temp /= i;
        }
    }
    if (temp > 1) {
        ans = ans / temp * (temp - 1);
    }
    return ans;
}

// 带有模数基底的快速幂
long long quick_power(long long base, long long exp, long long mod) {
    long long res = 1;
    base %= mod;
    while (exp > 0) {
        if (exp & 1) res = (__int128_t)res * base % mod; // __int128_t 严防乘法中途越界
        base = (__int128_t)base * base % mod;
        exp >>= 1;
    }
    return res;
}

int main() {
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);

    long long a, m;
    if (std::cin >> a >> m) {
        long long phi_m = get_phi(m);
        long long b = 0;
        bool over_limit = false;

        char ch;
        // 过滤前导非数字字符
        while (std::cin >> ch && (ch < '0' || ch > '9'));

        // 动态读入大数并执行扩展欧几里得降幂判定
        while (ch >= '0' && ch <= '9') {
            b = b * 10 + (ch - '0');
            if (b >= phi_m) {
                over_limit = true;
                b %= phi_m;
            }
            // 尝试读取下一个字符,若非数字则安全终止
            if (!(std::cin >> ch)) break; 
        }

        // 根据定理边界条件组装最终的指数状态
        long long final_exp = b;
        if (over_limit) {
            final_exp += phi_m;
        }

        long long ans = quick_power(a, final_exp, m);
        std::cout << ans << "\n";
    }
    return 0;
}

NOIP 实战避坑指南

  1. 指数大数读入时错套传统模运算
    部分选手在读入超大指数 $b$ 时,顺手写了 b = (b * 10 + ch - '0') % m。注意:指数必须模 $\varphi(m)$ 而不是模 $m$。一旦混淆,计算出的答案与正确结果将毫无关联。
  2. 忽略 $b < \varphi(m)$ 时的加法偏移破坏性质
    扩展欧几里得定理明确指出,只有在 $b \ge \varphi(m)$ 时,指数才能套用 $b \bmod \varphi(m) + \varphi(m)$。如果 $b$ 本身很小(比如 $b = 1$),而你盲目加上了 $\varphi(m)$,当 $\gcd(a, m) \neq 1$ 时,算出来的结果会比原版多乘了若干个因子,导致逻辑溃败。必须使用 over_limit 标记严格分支。

经典 NOIP/洛谷 真题

1. 洛谷 P5091 【模板】扩展欧拉定理

2. 洛谷 P4139 上帝与集合的正确用法

原文链接: local://19.5

[h] 返回首页