数论学习笔记之欧拉函数

欧拉函数

欧拉函数是什么

定义 欧拉函数 \(\varphi(n)\) 表示从 \(1\)\(n\) 中与 \(n\) 互质的数的个数

若在算数基本定理中 \(N=p_1 ^ {c_1}p_2 ^ {c_2}...p_m ^ {c_m}\) , 则有:

\[\varphi(N) = N * \frac{p_1 - 1} {p_1}* \frac{p_2 - 1} {p_2}...* \frac{p_m - 1} {p_m} \]

等同于

\[\varphi(N) = N * \prod_{i = 1} ^ {m}(1 - \frac{1}{p_i}) \]

证明

\(p\)\(N\) 的质因子, 那么1到N中p的倍数有 \(p, 2p, 3p, ..., (N/p) * p\), 一共有 \(N/p\)
同理, 我们设 \(q\) 也为 \(N\) 的另一个质因子, 那么 \(1\)\(N\) 中为 \(q\) 的倍数有 \(N/q\)

那么如果我们将 \(N/p + N/q\) 个数字去掉就会发现同时为 \(p\)\(q\) 的倍数的数字被去除了两次, 要加回来一次

然后我们就会得出如下式子:

\[N-\frac{N}{q} - \frac{N}{p} + \frac{N}{pq} = N(1 - \frac{1}{p} - \frac{1}{q} + \frac{1}{pq} ) = N(1-\frac{1}{p})(1 - \frac{1}{q}) \]

其实就是运用了容斥原理
同样的, 我们可以用这个方式去除其他质因子的倍数, 得到的就是 \(1\)\(N\) 中与 \(N\) 互质的数的个数, 最终推得上面的式子

证毕.

性质

积性函数

定义: \(a, b\)互质, 有 \(f(a)*f(b) = f(ab)\) , 那么就称 \(f\) 为积性函数

欧拉函数就是一个积性函数

那么欧拉函数就拥有了积性函数的性质, 即

  1. \(\varphi(ab) = \varphi(a)*\varphi(b)\)

  2. 当有 $ n = \prod_{i = 1} ^ {m} p_i ^ {c _ i}$, 则 \(\varphi(n) = \prod_ {i = 1} ^ {m} \varphi(p_i ^ {c_ i})\)

证明(1)

《算法竞赛进阶指南》上只有一句话:

根据欧拉函数的计算式, 对 \(a, b\) 进行分解质因数, 直接可得性质2. 把性质2推广到一般的函数上, 可以得到 "积性函数" 的概念.
证毕.

额, 怎么不算证明呢

本人时间有限实际就是懒得写, 读者可以自己根据这个证明一下, 这里不再进行其他说明

证明(2)

\(n\)分解质因数, 再结合积性函数的定义, 显然性质2成立.
证毕.

其他说明我觉得不太必要了, 有需要可以自己算算

欧拉函数的性质

  1. 上面有了

  2. 同上

  3. $ \forall n > 1$ , \(1\)\(n\) 中与 \(n\) 互质的数字和为 \(n * \varphi(n)/2\)

  4. \(n\) 为质数, 有\(\varphi(n) = n - 1\)

  5. \(n\) 为质数, 有 \(\varphi(n ^ k) = (n - 1) * n ^{k - 1} = n ^ k - n ^{k - 1}\)

  6. \(p\) 为质数, 若 \(p\mid n\) 但是 \(p ^ 2 \mid n\) , 则 \(\varphi(n) = \varphi(n/p) * p\)

  7. \(p\) 为质数, 若 \(p\mid n\) 但是 \(p ^ 2 \nmid n\) , 则 \(\varphi(n) = \varphi(n/p) * (p - 1)\)

  8. \(\sum _ {d \mid n} \varphi(d) = n\)

证明(3 ~ 6)

  1. 因为 \(gcd(n, x) = gcd(n, n - x)\) , 所以我们发现与
    \(n\) 不互质的两个数 \(x, n - x\) 总是成对出现, 平均值 \(n / 2\) , 得证.

  2. 显然

  3. 其实就是定义式的变形, 可以自己带个数算算, 也挺显然的

  4. \(p\) 为质数, \(p\mid n\) 但是 \(p ^ 2 \mid n\) , 则有\(n, n/p\) 包含相同的质因子, 因为积性函数, 只是 \(p\) 的指数不同. 直接写 \(\varphi (n)\)\(\varphi (n/p)\) 的公式, 两者相除得 \(p\) , 得证.

  5. \(p\) 为质数, 若 \(p\mid n\) 但是 \(p ^ 2 \nmid n\) , 说明 \(p, n/p\) 互质, 由积性函数得 \(\varphi(n) = \varphi(n/p) * \varphi(p)\) , 又有 \(\varphi(p) = p - 1\) , 得证.

  6. \(f(n) = \sum _{d \mid n} \varphi(d)\) .
    由乘法分配律和积性函数, 易得 \(f(nm) = \sum _ {d \mid nm} \varphi(d) = (\,\sum_ {d \mid n} \varphi(d)\,) * (\,\sum _{d \mid m} \varphi(d)\,) = f(n) * f(m)\).
    现在我们知道了我们设的函数为一个积性函数.
    那么对于单个质因子有 \(f(p ^ m) = \sum _ {d \mid p ^ m} = \varphi(1) + \varphi(p) + \varphi(p ^ 2) + ... + \varphi(p ^ {m - 1})\), 相当于一个等比函数再加一, 最终为 \(p ^ m\) .
    那么就有 \(f(n) = \prod _ {i = 1} ^ {m} f(p_i ^{c_i}) = \prod _ {i = 1} ^ {m} p_i ^ {c _ i} = n\) .
    证毕.

求欧拉函数

累死了终于不用到处证明了

试除法

都试除法了, 那肯定是使用的唯一分解定理啦

我们直接把之前推过的式子搬下来

\[\varphi(N) = N * \prod_{i = 1} ^ {m}(1 - \frac{1}{p_i}) \]

显然, 上面的式子等同于:

\[\varphi(N) = N * \prod_{p \mid N} (1 - \frac{1}{p}) \]

\(p \mid N\) 表示的就是N的质因子

欧拉函数仅由 \(n\) 与质因子决定, 与次数无关

分解质因数的复杂度为 \(O(\sqrt n)\) , 计算欧拉函数的复杂度为 \(O(\sqrt n)\) , 若要求 \(2\) ~ \(n\) 的欧拉函数的话就是 \(O(n \sqrt n)\)

int phi(int n) { //试除法求欧拉函数(n)
    int res = n;

    for (int i = 2; i * i <= n; i++){
        if (n % i == 0) res = res / i * (i - 1);
        while (n % i == 0) n /= i;
    }

    if (n > 1) res = res / n * (n - 1);

    return res;
}

埃氏筛

同样使用欧拉函数的计算公式

求出 \(2\) ~ \(n\) 的欧拉函数的时间复杂度为 \(O(n \sqrt n)\)

void varphii(int n) { //埃氏筛求欧拉函数(2 ~ n)
    for (int i = 2; i <= n; i++) phi[i] = i;

    for (int i = 2; i <= n; i++) 
        if (phi[i] == i)
            for (int j = i; j <= n; j += i)
                phi[j] = phi[j] / i * (i - 1);
}

线性筛

用以上两种算法已经能解决大部分求欧拉函数的问题

但是有没有一种算法能优化时间复杂度呢?

有的兄弟, 有的

用线性筛的思想就能将求出 \(2\) ~ \(n\) 的欧拉函数的时间复杂度降到 \(O(n)\)

在线性筛中, 每个数都只会被自己的最小的质因子筛掉
结合我们之前列出的性质6与性质7

  1. \(p\) 为质数, 若 \(p\mid n\) 但是 \(p ^ 2 \mid n\) , 则 \(\varphi(n) = \varphi(n/p) * p\)
  2. \(p\) 为质数, 若 \(p\mid n\) 但是 \(p ^ 2 \nmid n\) , 则 \(\varphi(n) = \varphi(n/p) * (p - 1)\)

我们就可以用 \(\varphi(n / p)\) 推出 \(\varphi(n)\)

然后就是你们最喜欢的代码环节

int phi[MN], vis[MN], p[MN], cnt;
//欧拉函数; 访问标记(未标记的为质数); 质数; 质数计数器

void varphi(int n){ //线性筛求欧拉函数(1 ~ n)
    phi[1] = 1;
    for (int i = 2; i <= n; i++){
        if (vis[i] == 0){
            p[++cnt] = i; //下标从1开始!! 
            phi[i] = i - 1; //若为质数则减一 
        }

        for (int j = 1; j <= cnt; j++){
            if (i * p[j] > n) break;

            int m = i * p[j];
            vis[m] = 1; //非质数标记 
            if (i % p[j] == 0){ //i能被p[j]整除则i包含了m的所有质因子 
                phi[m] = phi[i] * p[j];
                break;
            }
            else { //不能整除说明互质 
                phi[m] = phi[i] * (p[j] - 1); //注: phi[j] = p[j] - 1
            }
        }
    }
}

例题

洛谷P2158 仪仗队

题目描述

作为体育委员,C 君负责这次运动会仪仗队的训练。仪仗队是由学生组成的 \(N \times N\) 的方阵,为了保证队伍在行进中整齐划一,C 君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图)。

示意图

现在,C 君希望你告诉他队伍整齐时能看到的学生人数。

输入格式

一行,一个正整数 \(N\)

输出格式

输出一行一个数,即 C 君应看到的学生人数。

输入输出样例 #1

输入 #1

4

输出 #1

9

说明/提示

对于 \(100 \%\) 的数据,\(1 \le N \le 40000\)

解题思路

我们仔细观察上图, 注意到若一个学生能被看到, 那么他所站的位置距离C君的水平距离 \((x)\) 和竖直距离 \((y)\) 满足 \(gcd(x, y) = 1\)

那么 当 \(n > 2\)\(n\) 每增大 \(1\) , 在同一行或同一列上新的能看到的学生数量就是与 \(n - 1\) 互质的学生数量, 即 \(\varphi (n - 1)\) , 可以自己列一下感受一下

我们还发现像这样的学生总是成对出现, 结合打表 (我认真的) , 很容易就能得出这样的式子 (我这里设了个函数 \(f\) 表示答案) :

\[f(n) = \begin {cases} 1 + \sum _ {i = 2} ^ {n - 1} 2\varphi (i), &\text{n > 1}\\ 0, &\text {n = 1} \end{cases} \]

然后...上代码吧

代码

#include <bits/stdc++.h>
using namespace std;

const int MN = 40003;
int n, ans = 1;
int phi[MN], vis[MN], p[MN], cnt; 

int main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    cin >> n;
    if (n == 1) {
        cout << 0 << endl;
        return 0;
    }

    phi[1] = 1;
    for (int i = 2; i < n; i++){
        if (vis[i] == 0){
            p[++cnt] = i;
            phi[i] = i - 1;
        }

        for (int j = 1; j <= cnt; j++){
            if (i * p[j] > n) break;

            int m = i * p[j];
            vis[m] = 1;
            if (i % p[j] == 0){
                phi[m] = phi[i] * p[j];
                break;
            }
            else {
                phi[m] = phi[i] * (p[j] - 1);
            }
        }
    }

    for (int i = 1; i < n; i++) ans += phi[i] * 2;

    cout << ans;

    return 0;
} 

内容参考《算法竞赛进阶指南》与上课的课件

posted @ 2025-06-09 13:24  StarLinkOvO  阅读(62)  评论(0)    收藏  举报