```mermaid graph LR A[Mermaid Keeper] --> B[保持激活] ```

前言

02-01 整数与整除 里我们学了 GCD,在 02-02 素数与筛法 里学了筛法。现在把两者结合:给定 NN,1 到 NN 中有多少个和 NN 互质的数?

这个数量叫欧拉函数 φ(N)\varphi(N)。它不只是个数学定义——费马小定理的推广(欧拉定理)需要它,模意义下求逆元需要它,计算原根个数也需要它。

问题的本质

定义

φ(N)\varphi(N) = {1,2,,N}\{1, 2, \ldots, N\} 中与 NN 互质(gcd(k,N)=1\gcd(k, N) = 1)的数的个数。

手动算几个:

NNNN 互质的数φ(N)\varphi(N)
1{1}1
2{1}1
3{1,2}2
4{1,3}2
5{1,2,3,4}4
6{1,5}2
7{1,2,3,4,5,6}6
12{1,5,7,11}4

有什么规律?素数 pp 的欧拉函数是 p1p-1——因为 1 到 p1p-1 都和 pp 互质。p2p^2 的欧拉函数是 p2pp^2-p——因为 1 到 p2p^2 中只有 pp 的倍数不互质,有 pp 个。

唯一分解与公式

由算术基本定理,N=p1a1p2a2pkakN = p_1^{a_1} p_2^{a_2} \cdots p_k^{a_k}。则:

φ(N)=NpN(11p)=NpNp1p\varphi(N) = N \prod_{p \mid N} \left(1 - \frac{1}{p}\right) = N \prod_{p \mid N} \frac{p-1}{p}

直觉理解:在 1 到 NN 中,是 pp 的倍数的占 1/p1/p,所以不是 pp 的倍数的占 (11/p)(1-1/p)。多个素因子独立(因为素数两两互质),所以乘起来。

验证N=12=22×3N=12=2^2 \times 3φ(12)=12×12×23=4\varphi(12) = 12 \times \frac{1}{2} \times \frac{2}{3} = 4。✓

积性

φ\varphi积性函数:如果 gcd(a,b)=1\gcd(a,b)=1,则 φ(ab)=φ(a)×φ(b)\varphi(ab) = \varphi(a) \times \varphi(b)

这意味着:只要能算 φ(pk)\varphi(p^k),就能算任意 NN 的欧拉函数。

φ(pk)=pkpk1=pk1(p1)\varphi(p^k) = p^k - p^{k-1} = p^{k-1}(p-1)

理论 + 代码

单点计算 O(N)O(\sqrt{N})

long long euler_phi(long long n) {
    long long ans = n;
    for (long long p = 2; p * p <= n; p++) {
        if (n % p == 0) {
            ans = ans / p * (p - 1);    // ① 乘 (p-1)/p
            while (n % p == 0) n /= p;  // ② 去掉所有 p 因子
        }
    }
    if (n > 1) ans = ans / n * (n - 1); // ③ 剩下的大于√n 的素因子
    return ans;
}
  • ans / p * (p-1):先除后乘防溢出。
  • ② 除干净——因为同一个素因子只处理一次。
  • ③ 如果 nn 还剩一个大于 N\sqrt{N} 的素因子(比如 N=14=2×7N=14=2\times7,处理完 2 后 n=7n=7)。

走一遍过程φ(60)\varphi(60)60=22×3×560 = 2^2 \times 3 \times 5

  • p=2p=2ans = 60/2*(2-1) = 30nn 变为 15。
  • p=3p=3ans = 30/3*(3-1) = 20nn 变为 5。
  • p=5p=55*5=25 > 5,循环结束。
  • n=5>1n=5>1ans = 20/5*(5-1) = 16

φ(60)=16\varphi(60)=16。验证:60=22×3×560=2^2\times3\times560×12×23×45=1660\times\frac{1}{2}\times\frac{2}{3}\times\frac{4}{5}=16。✓

筛法求所有 φ(1)\varphi(1)φ(N)\varphi(N)——O(NloglogN)O(N \log \log N)

类似 02-02 素数与筛法 中的欧拉筛:

int phi[1000006];
void euler_sieve(int N) {
    for (int i = 0; i <= N; i++) phi[i] = i;
    for (int i = 2; i <= N; i++) {
        if (phi[i] == i) {                // ① i 是素数
            for (int j = i; j <= N; j += i) {
                phi[j] = phi[j] / i * (i - 1);
                // ② 对每个 i 的倍数,乘 (i-1)/i
            }
        }
    }
}
  • ① 如果 phi[i] == i,说明 ii 还没被任何素数处理过,即 ii 是素数。
  • ② 对 ii 的每个倍数 jj,把 phi[j] 乘上 (i1)/i(i-1)/i。这对应公式 φ(N)=NpNp1p\varphi(N) = N \prod_{p|N} \frac{p-1}{p}

例题

例题 1:M&A 014 — Factorization(★1)

题目:对整数 NN 进行素因数分解,按从小到大的顺序输出所有素因子(重复的重复输出)。

数据范围2N10122 \le N \le 10^{12}

—— AtCoder M&A 014

思路:素因数分解是欧拉函数的基础。欧拉函数 φ(N)=NpN(11/p)\varphi(N) = N \prod_{p|N}(1 - 1/p) 的计算需要先做素因数分解。

代码

#include <cstdio>
using namespace std;

int main() {
    long long N;
    scanf("%lld", &N);

    for (long long i = 2; i * i <= N; i++) {
        while (N % i == 0) {
            printf("%lld ", i);
            N /= i;
        }
    }
    if (N > 1) printf("%lld", N);
    printf("\n");
}

例题 2:Tessoku Book — A31 Divisors

题目:给定正整数 NN,求 NN 的正约数个数。

数据范围1N10121 \le N \le 10^{12}

—— AtCoder Tessoku Book A31

思路:先素因数分解 N=p1e1×p2e2×N = p_1^{e_1} \times p_2^{e_2} \times \cdots,则约数个数 d(N)=(e1+1)(e2+1)d(N) = (e_1+1)(e_2+1) \cdots

代码

#include <cstdio>
using namespace std;

int main() {
    long long N;
    scanf("%lld", &N);

    long long ans = 1;
    long long temp = N;
    for (long long i = 2; i * i <= temp; i++) {
        int e = 0;
        while (temp % i == 0) {
            e++;
            temp /= i;
        }
        if (e > 0) ans *= (e + 1);  // ① 每个素因子的指数+1 相乘
    }
    if (temp > 1) ans *= 2;  // ② 剩下一个大于√N的素因子,指数为1

    printf("%lld\n", ans);
}

逐行解析

  • ① 如果 N=p1e1×p2e2N = p_1^{e_1} \times p_2^{e_2},则每个素因子可以选择 00eie_i 次,共 ei+1e_i + 1 种选择。总约数个数 = 所有 (ei+1)(e_i + 1) 的乘积。
  • ② 如果最后 temp > 1,说明还有一个大于 N\sqrt{N} 的素因子,指数为 1,贡献 1+1=21+1=2

例题 3:欧拉函数的计算公式(理论推导)

题目:给定 NN,用欧拉函数公式 φ(N)=NpN(11/p)\varphi(N) = N \prod_{p|N}(1 - 1/p) 计算 φ(N)\varphi(N)

数据范围2N10122 \le N \le 10^{12}

思路:先做素因数分解,对每个不同的素因子 pp,把答案乘以 (11/p)=(p1)/p(1 - 1/p) = (p-1)/p。为了避免浮点数,用整数运算:φ(N)=N×pNp1p\varphi(N) = N \times \prod_{p|N} \frac{p-1}{p}

代码

#include <cstdio>
using namespace std;

int main() {
    long long N;
    scanf("%lld", &N);

    long long ans = N;
    long long temp = N;
    for (long long i = 2; i * i <= temp; i++) {
        if (temp % i == 0) {
            ans = ans / i * (i - 1);  // ① 乘以 (i-1)/i
            while (temp % i == 0)
                temp /= i;            // ② 除尽所有 i
        }
    }
    if (temp > 1)
        ans = ans / temp * (temp - 1);  // ③ 最后一个素因子

    printf("%lld\n", ans);
}

逐行解析

  • ans / i * (i - 1):先除后乘防溢出。ans 一定是 i 的倍数(因为 iNi|N 且初始 ans = N),所以整数除法不会丢精度。
  • ② 对每个素因子只处理一次(除尽后 temp 不再包含 i)。
  • ③ 和 ① 一样,处理最后一个大于 N\sqrt{N} 的素因子。

验证N=12=22×3N = 12 = 2^2 \times 3φ(12)=12×12×23=4\varphi(12) = 12 \times \frac{1}{2} \times \frac{2}{3} = 4。实际 φ(12)={1,5,7,11}=4\varphi(12) = |\{1, 5, 7, 11\}| = 4。✓


例题 4:T90 038 — Large LCM(★3)

题目:给定两个正整数 A,BA, B,求 lcm(A,B)mod(1018+9)\text{lcm}(A, B) \bmod (10^{18}+9)(或判断是否超过 101810^{18})。

数据范围1A,B10181 \le A, B \le 10^{18}

—— AtCoder T90 038

思路lcm(A,B)=A/gcd(A,B)×B\text{lcm}(A,B) = A / \gcd(A,B) \times B。但 A,B1018A, B \le 10^{18},乘积可能超过 long long 范围。需要先检查是否溢出:如果 A/gcd(A,B)>1018/BA / \gcd(A,B) > 10^{18} / B,则 LCM 超过 101810^{18}

代码

#include <cstdio>
using namespace std;

long long gcd(long long a, long long b) {
    while (b) { long long t = b; b = a % b; a = t; }
    return a;
}

int main() {
    long long A, B;
    scanf("%lld%lld", &A, &B);

    long long g = gcd(A, B);
    A /= g;

    if (A > 1000000000000000000LL / B)  // ① 检查 A/g * B 是否溢出
        printf("Large\n");
    else
        printf("%lld\n", A * B);
}

逐行解析

  • A > 10^{18} / B 等价于 A×B>1018A \times B > 10^{18},但不用乘法(避免溢出),改用除法判断。

例题 5:M&A 042 — Sum of Divisors(★2)

题目:设 f(X)f(X)XX 的正约数个数。求 K=1NK×f(K)\sum_{K=1}^{N} K \times f(K)

数据范围1N1071 \le N \le 10^7

—— AtCoder M&A 042

思路:和 02-03 例题 5 一样。这道题同时出现在多个章节——从”欧拉函数/约数”角度,它考察的是约数函数 d(n)d(n) 的求和。

代码:(见 02-03 例题 5)

从欧拉函数的角度看,K=1Nf(K)\sum_{K=1}^{N} f(K) 就是 d=1NN/d\sum_{d=1}^{N} \lfloor N/d \rfloor——枚举每个约数,统计它作为多少个数的约数出现。这种”转换视角”(从”每个数的约数”变成”每个约数对应的数”)是数论中的核心技巧。

参考文献

理论讲义 — AtCoder Algorithm Lectures

教材讲解 — 競技プログラミングの鉄則 第 5 章

基础练习 — アルゴリズムと数学 演習問題集

系统练习 — 競技プログラミングの鉄則

实战练习 — 競プロ典型 90 問


系列索引

第零章 基础工具

第一章 搜索技术

第二章 数学基础

第三章 数据结构

第四章 图论

第五章 动态规划

第六章 贪心

第七章 字符串

第八章 进阶