• 欧拉函数


    「观前提醒」

    「文章仅供学习和参考,如有问题请在评论区提出」





    定义#


    欧拉函数的符号表示是 φ(n) ,表示 1n 中和 n 互质的数的个数。

    例如,φ(12)=4,即 1,5,7,11


    性质#


    1. n 是质数, 则 φ(n)=n1

      质数会和小于它本身的所有正整数互质,即 n1n1 中所有数互质。

    2. n 是奇数时,φ(2n)=φ(n)

      只有这一种情况成立,并不是 n 的偶数倍的意思。

    3. 如果 n=pk,其中 p 是质数,那么

      (1)φ(n)=pkpk1(2)=pk1(p1)(3)=pk(11p)

      1n 中只有不包含质数 p,才会与 n 互质。而包含质数 p 的数为 p 倍数,即 1p,2p,3p,4p,...,pk1p ,总共有 pk1 个。

      所以去掉包含 p 的数,就是和 n 互质的数的个数,即 φ(n)=pkpk1

      公式变形,就会有上述三个表示方式。

    4. 积性函数:若 gcd(a,b)=1,则 φ(ab)=φ(a)φ(b)


    计算公式推导#


    由唯一分解定理,

    n=i=1kpiai=p1a1p2a2p3a3...pkak

    pin 的质因子

    提醒:ab 是乘积运算符号,代表 ab 所有数的乘积,即 a×(a+1)×...×b

    那么有,

    φ(n)=φ(i=1kpiai)

    因为对于任意的 piaipjaj(1i,jk) 都是互质的,所以用到上面的性质4:若 gcd(a,b)=1,则 φ(ab)=φ(a)φ(b) 。那么可以推出,

    (4)φ(n)=φ(i=1kpiai)(5)=i=1kφ(piai)

    然后再根据性质3,推出

    (6)φ(n)=φ(i=1kpiai)(7)=i=1kφ(piai)(8)=i=1kpiai1(pi1)(9)=i=1kpiai(11pi))

    最后进行公式变形,可得

    (10)φ(n)=φ(i=1kpiai)(11)=i=1kφ(piai)(12)=i=1kpiai1(pi1)(13)=i=1kpiai(11pi)(14)=i=1kpiai×i=1k(11pi)(15)=n×i=1kpi1pi(16)=n×p11p1×p21p2×...×pk1pk

    公式进行整理,可得

    (17)φ(n)=n×i=1kpi1pi(18)=n×p11p1×p21p2×...×pk1pk

    观察公式就能发现,欧拉函数仅与 n 及其质因子有关


    求欧拉函数#



    分解质因子法#


    思路:用试除法分解出 n 的所有质因子,然后根据推导的公式求解一个数的欧拉函数。

    时间复杂度O(n)

    代码

    // 分解质因数求欧拉函数
    int phi(int n) {
        int res = n;
        // 分解质因子
        for (int i = 2; i <= n / i; i++) {
            if (n % i == 0) {
                // 公式求值
                res = res / i * (i - 1);
                while (n % i == 0) n /= i;
            }
        }
        if (n > 1) res = res / i * (i - 1);
        
        return res;
    }
    

    筛法求欧拉函数#


    // 线性筛法求 1 ~ n 的 质数
    
    const int N = 1e5 + 10;
    int p[N], cnt;	// p[]存储所有素数
    bool st[N];	 // st[x]存储x是否被筛掉
    
    void get_primes(int n) {
    	st[1] = true;
        
        for (int i = 2; i <= n; i++) {
            if (!st[i]) p[cnt++] = i;
            for (int j = 1; p[j] <= n / i; j++) {
                st[p[j] * i] = true;
                if (i % p[j] == 0) break;
            }
        }
    }
    

    思路

    观察上面的线性筛质数的代码,我们可以再用一个 phi[] 来存储每一个数的欧拉函数,即 phi[i]=φ(i)

    i 是质数,根据性质1可得 phi[i]=i1

    而且在线性筛中,每个合数(非质数)都会被自身的最小质因子筛掉。那么设 pjm 的最小质因子,根据线性筛,就想办法让 m 通过 m=pj×i 筛掉。

    • i 能被 pj 整除,则 i 就包含了 m 的所有质因子。

      i 能被 pj 整除,说明 pj 也是 i 的质因子。又因为 pj 也是 m 的质因子,而且 m=pj×i ,所以 i 就包含了 m 的所有质因子。

      然后再根据推导的公式变形,得

      (19)φ(m)=m×k=1Spk1pk(20)=pj×i×k=1Spk1pk(21)=pj×φ(i)

      根据推导公式,一个数得欧拉函数只与其本身和其质因子有关。

      虽然 k=1Spk1pk 是从 φ(m) 推导出来的,但是因为 im 的质因子相同,所以也可以被用来推导出 φ(i)

    • i 不能被 pj 整除,则 ipj 是互质的。

      因为 pj 是质数,除了 pj 本身,就没有其他的质因子。若 i 不能被 pj 整除,那么 ipj 就没有相同的质因子,那么两者就是互质的。

      然后根据性质4和性质1进行公式变形,得

      (22)φ(m)=φ(pj×i)(23)=φ(pj)×φ(i)(24)=(pj1)×φ(i)

    总结上面的思路,就能得到所有的情况,

    • i 是 质数,得 φ(i)=i1
    • i 不是质数,说明已经被自己的质因子赋值了。
    • 遍历 p[1cnt]
      • i 能被 pj 整除,得 φ(m)=pj×φ(i) ,并退出遍历。
      • i 不能被 pj 整除,得 φ(m)=(pj1)×φ(i)

    时间复杂度O(N)

    代码

    const int N = 1e5 + 10;
    int p[N], cnt;	// p[] 存储所有素数
    int phi[N];	// phi[x] 存储 x 的欧拉函数值
    bool st[N];	// st[x] 存储 x 是否被筛掉
    
    // 线性筛法求欧拉函数
    void get_phi(int n) {
        phi[1] = 1;
        st[1] = true;
        
        for (int i = 2; i <= n; i++) {
            // 没有被筛过,说明是质数
            if (!st[i]) {
                p[cnt++] = i;
                phi[i] = i - 1;
            }
            
            for (int j = 0; p[j] <= n / i; j++) {
                int m = i * p[j];
              	st[m] = true;
                // 判断是否能整除,然后根据公式赋值
                if (i % p[j] == 0) {
                    phi[m] = p[j] * phi[i];
                    break;
                } else phi[m] = (p[j] - 1) * phi[i];
            }
        }
    }
    


    参考资料#


    欧拉函数 - OI Wiki (oi-wiki.org):https://oi-wiki.org/math/number-theory/euler/

    【RSA原理2】浅谈--什么是欧拉函数 韦_恩的博客-CSDN博客:https://blog.csdn.net/qq_42539194/article/details/118514310

    董晓算法 515 筛法求欧拉函数:https://www.bilibili.com/video/BV1VP411p7Bs



  • 相关阅读:
    基于单片机设计的家用自来水水质监测装置
    springboot整合xxl-job分布式定时任务【图文完整版】
    python-flask笔记
    一步一步教你如何在Windows 10上使用Java,包括下载、安装和配置等
    ‘vue‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件 出现这个问题如何解决
    迅为RK3588在 Linux 系统中使用 NPU
    Unreal Engine游戏引擎的优势
    渗透测试-渗透测试常见的总结
    JavaSE---栈和队列
    【二叉树系列】插入&删除&修剪&有序数组转二叉搜索树&二叉搜索树转换为累加树
  • 原文地址:https://www.cnblogs.com/oneway10101/p/17565867.html