古典叙事 · 技术札记

序章

山河有卷
人间有声

写代码,也写长风、旧城与未熄的灯。
愿每一篇随笔,都有自己的山水与回声。
阅览随笔

欧拉函数与欧拉筛

欧拉函数

  1. 欧拉函数 \(\phi(n)\) 的数学灵魂定义:\(\phi(n)\) 表示在 \([1, n]\) 范围内,与 \(n\) 互质的整数的个数。它是数论中最基础的函数之一,具有三个在代码状态转移中极为关键的性质(它是一个积性函数):
    性质 1(质数基底):如果 \(p\) 是质数,那么除了 \(p\) 自己,比它小的所有数都和它互质。结论:\(\phi(p) = p - 1\)
    性质 2(互质相乘):如果两个数 \(a\)\(b\) 互质(即 \(\gcd(a, b) = 1\)),它们的欧拉函数可以拆开相乘。结论:\(\phi(a \times b) = \phi(a) \times \phi(b)\)
    性质 3(质因子倍增):如果 \(a\) 已经是质数 \(p\) 的倍数了(即 \(a \pmod p = 0\)),这就意味着 \(a \times p\) 的质因子种类和 \(a\) 完全一样,只是数量变了。结论:\(\phi(a \times p) = \phi(a) \times p\)

线性筛

  1. 线性筛(欧拉筛)的核心机制目标:在 \(O(N)\) 的时间复杂度内,找出 \([1, N]\) 范围内的所有质数,并打上合数标记。痛点:传统的埃氏筛(Eratosthenes)中,像 \(12\) 这样的数字,会被 \(2\) 筛一次,被 \(3\) 筛一次,导致重复劳动,复杂度是 \(O(N \log \log N)\)
    线性筛的终极法则:每一个合数,都只能被它“最小的质因子”筛掉。下面是它的核心逻辑框架:从小到大遍历每一个数字 \(i\)。无论 \(i\) 是质数还是合数,都用 \(i\) 去乘以当前已经找到的所有质数 \(P_j\),以此来筛掉后续的合数(即 \(i \times P_j\))。破局点(Break 机制):当遇到 i % P_j == 0 时,立刻停止内层循环!

求欧拉函数

模板一:求单个数的欧拉函数 \(O(\sqrt{N})\)适用场景:只求一个数字 \(N\) 的欧拉函数,且 \(N\) 比较大(比如 \(N \le 10^{12}\))。核心公式:根据欧拉函数的通项公式,设 \(N\) 的所有质因子为 \(p_1, p_2, \dots, p_k\),则:$$\phi(N) = N \times (1 - \frac{1}{p_1}) \times (1 - \frac{1}{p_2}) \dots \times (1 - \frac{1}{p_k})$$算法逻辑:
利用试除法,从 \(2\)\(\sqrt{N}\) 遍历找质因子。找到一个质因子 \(p\) 后,先更新答案,然后把 \(N\) 里面所有的 \(p\) 统统除干净。最后如果 \(N > 1\),说明剩下一个大于 \(\sqrt{N}\) 的质因子,单独处理即可

#include <iostream>

using namespace std;
using i64 = long long;

// 求单个数 n 的欧拉函数
i64 get_phi(i64 n) {
    i64 res = n;
    for (i64 i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            // 找到一个质因子 i,更新公式结果
            // 注意:写成 res = res / i * (i - 1) 是为了防止先乘导致溢出
            res = res / i * (i - 1); 
            
            // 把 n 中所有的质因子 i 除干净
            while (n % i == 0) {
                n /= i;
            }
        }
    }
    // 如果最后 n > 1,说明 n 本身就是一个大于 sqrt(最初n) 的质数
    if (n > 1) {
        res = res / n * (n - 1);
    }
    return res;
}

模板二:线性筛求 \(1 \sim N\) 欧拉函数数组 \(O(N)\)适用场景:需要频繁查询多个数的欧拉函数,或者需要求欧拉函数前缀和,且 \(N\) 的范围可以开得下数组(通常 \(N \le 10^7\))。核心逻辑:就是我们上一回合详细拆解过的线性筛打表法。

#include <iostream>
#include <vector>

using namespace std;

const int MAXN = 10000005;

int primes[MAXN], cnt = 0; // primes 存质数,cnt 记录质数个数
bool is_prime[MAXN];       // 标记是否为质数
int phi[MAXN];             // 存每个数的欧拉函数值

void euler_sieve(int n) {
    // 1. 初始化
    for (int i = 2; i <= n; i++) {
        is_prime[i] = true;
    }
    phi[1] = 1;

    // 2. 线性筛主体
    for (int i = 2; i <= n; i++) {
        if (is_prime[i]) {
            primes[cnt++] = i;
            phi[i] = i - 1; // 质数的欧拉函数
        }
        
        // 遍历已经找到的质数
        for (int j = 0; j < cnt; j++) {
            int p = primes[j];
            if (i * p > n) break; // 越界退出
            
            is_prime[i * p] = false; // 筛掉合数
            
            if (i % p == 0) {
                // p 是 i 的最小质因子
                phi[i * p] = phi[i] * p;
                break; // 【核心跳出机制】
            } else {
                // p 和 i 互质
                phi[i * p] = phi[i] * (p - 1);
            }
        }
    }
}

1. 互质数之和性质(对称性)定理:

对于任意整数 \(n > 1\),在 \(1\)\(n\) 中所有与 \(n\) 互质的数的总和为:$$\text{Sum} = \frac{n \times \phi(n)}{2}$$欧拉函数有一个极美的对称性:如果 \(\gcd(x, n) = 1\),那么必定有 \(\gcd(n-x, n) = 1\)。这意味着,与 \(n\) 互质的数总是成对出现的,且每一对的和恰好是 \(n\)(即 \(x + (n-x) = n\))。既然一共有 \(\phi(n)\) 个数,即有 \(\frac{\phi(n)}{2}\) 对,那么它们的总和自然就是 \(n \times \frac{\phi(n)}{2}\)。实战场景:题目要求你算 \(\sum_{i=1}^n i \times [\gcd(i, n) == 1]\),如果你用 \(O(N)\) 遍历就上当了,直接用这个 \(O(1)\) 的公式秒杀。

2. 高斯等式(Dirichlet 卷积基础)定理:

一个正整数 \(n\) 的所有正约数的欧拉函数之和,等于 \(n\) 本身。$$\sum_{d\vert{}n} \phi(d) = n$$它的本质是把 \(1\)\(n\)\(n\) 个分数 \(\frac{1}{n}, \frac{2}{n}, \dots, \frac{n}{n}\) 进行约分。约分后,分母变成了 \(n\) 的各个约数 \(d\),而以 \(d\) 为分母的最简分数的个数,恰好就是 \(\phi(d)\)。实战场景:在莫比乌斯反演和推导 \(\gcd\) 相关的求和式时,只要看到孤立的变量 \(n\) 或者 \(\gcd(i,j)\),直接把它强行替换成 \(\sum_{d\vert{}n} \phi(d)\),往往能起到拨云见日的效果。

3. 欧拉定理(基础降幂)定理:

如果 \(a\)\(n\) 互质(即 \(\gcd(a, n) = 1\)),那么:$$a^{\phi(n)} \equiv 1 \pmod n$$这是著名的费马小定理的推广(当 \(n\) 为质数 \(p\) 时,\(\phi(p) = p-1\),就变成了 \(a^{p-1} \equiv 1 \pmod p\))。它是现代密码学(如 RSA 加密算法)的数学基石。实战场景:计算超大次方的取模,比如 \(7^{10^{100}} \pmod{10}\)。因为 \(\gcd(7, 10) = 1\),且 \(\phi(10) = 4\)。所以 \(7^4 \equiv 1 \pmod{10}\)。那么 \(7^{10^{100}} = 7^{4 \times 25\dots} = (7^4)^{25\dots} \equiv 1^{25\dots} \equiv 1 \pmod{10}\)。利用这个定理,指数不管有多大,都可以直接对 \(\phi(n)\) 取模:\(a^b \equiv a^{b \bmod \phi(n)} \pmod n\)

4. 扩展欧拉定理(终极降幂神器)

欧拉定理很完美,但它有一个致命缺陷:要求 \(a\)\(n\) 必须互质。如果 \(\gcd(a, n) \neq 1\) 怎么办?定理(扩展欧拉定理):对于任意整数 \(a\)\(n\)(无论是否互质),只要指数 \(b \ge \phi(n)\),就有:$$a^b \equiv a^{(b \bmod \phi(n)) + \phi(n)} \pmod n$$这个定理在算法竞赛中极为实用。无论底数和模数是什么关系,你都可以肆无忌惮地把极其庞大的指数 \(b\) 变小。注意,降幂后的指数不是简单的取模,而是取模后再加回一个 \(\phi(n)\)。实战场景:题目给定非常大的指数,甚至指数本身也是一个乘方(例如计算 \(A^B \pmod M\),其中 \(B\) 是一个超过 long long 范围的大数字符串)。你可以在读入字符串 \(B\) 的同时,边读边对 \(\phi(M)\) 取模,最后套用此定理进行 \(O(\log N)\) 的快速幂计算。

posted @ 2026-07-23 20:34  Morphis‘  阅读(2)  评论(0)    收藏  举报

特别策划 · CINEMATIC NOTES

风沙与孤骑

风起塞外,
胜负在刀剑之前。

“把复杂拆成秩序,把未知写成答案。”
Morphis · 山河一卷 愿你从这里出发,仍能听见山风。 影像:farfarSébastien Goldberg / Unsplash