欧拉函数(Euler Function)

Cover

欧拉函数

在数论,对正整数n,欧拉函数是小于n的正整数中与n互质的数的数目(φ(1)=1)。此函数以其首名研究者欧拉命名(Euler'so totient function),它又称为Euler's totient function、φ函数、欧拉商数等。

定理

  • phi(1) = 1
  • 如果n是质数的话,phi(n) = n - 1
  • 设m和n是互质的正整数,那么phi(mn) = phi(m) * phi(n)
  • 当n为奇数时,phi(2n) = phi(n)
  • 计算公式 phi(n) = n * (1 - 1 / p1) \ * (1 - 1 / p2) * … * (1 - 1 / pk)

根据计算公式的代码实现

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

由分析可以知道,这个函数的时间复杂度为O(n),当n达到1e9,肯定会超时。由于任何一个合数都存在着至少一个不大于sqrt(n)的质因数,所以只需遍历到sqrt(n)即可,这样时间复杂度为O(sqrt(n))。

改良代码

int phi(int n)
{
    int res = n;
    for(int i = 2; i * i <= n; i++) //降低时间复杂度
    {
        if(n % i == 0)
        {
            n/=i;
            res = res - res / i;
        }
        while(n % i == 0)
            n/=i;
    }
    if(n > 1) // 因为是遍历到sqrt(n),所以可能存在未除尽或者n本身就为质数的情况
        res = res - res / n; 
    return res;
}

欧拉函数的应用

[POJ 2407 Relatives](2407 — Relatives)

题意 输出输入数据的欧拉函数数值 裸欧拉函数题

#include<iostream>
using namespace std;
int n;
int phi(int n)
{
    int res = n;
    for(int i = 2; i * i <= n; i++)
    {
        if(n % i == 0)
        {
            n/=i;
            res = res - res / i;
        }
        while(n % i == 0)
            n/=i;
    }
    if(n > 1)
        res = res - res / n; 
    return res;
}
int main()
{
    while(scanf("%d",&n)!=EOF && n)
    {
        printf("%d\n",phi(n));
    }
    return 0;
}

HDU 2588 GCD

题意 求满足 1<=x<=N GCD(xi , N) = a >= M 的个数
思路
由于本题数据范围达到1e9,正常GCD累加做法肯定会超时,所以推导公式
GCD(a * pi , N) = a => GCD(pi , N/a) = 1 => 求 1~N/a的欧拉函数的和
根据定理得 N%i == 0 phi(N/i) N%(N/i) == 0 phi(i)

#include<iostream>
using namespace std;
int T,n,m,sum;
int phi(int x)
{
    int res = x;
    for(int i = 2; i * i <= x; i++)
    {
        if(x % i == 0)
        {
            res = res - res / i;
            x/=i;
        }
        while(x % i == 0)
            x/=i;
    }
    if(x > 1)
        res = res - res / x; 
    return res;
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        sum = 0;
        scanf("%d%d",&n,&m);
        for(int i = 1; i * i <= n; i++)//降低时间复杂度所以遍历到sqrt(n)
        {
            if(n % i == 0)
            {
                if(i >= m)//计算根号左边的欧拉函数值
                    sum += phi(n/i);
                if(n / i != i && n / i >= m)//判断是否重复数,避免重复计算 计算根号右面的欧拉函数值
                    sum += phi(i);
            }
        }
        printf("%d\n",sum);
    }
    return 0;
}

HDU 2824 The Euler function

题意 计算给定范围内欧拉函数值的前缀和
思路 根据计算公式 使用筛法求素数近似方法来打表计算每个数的欧拉函数值
解释
计算1~8的欧拉函数值
phi(1) = 1,所以从2开始 phi(2) = 2 * (1 - 1 / 2) = 1 phi(4) = 4 * (1 - 1 / 2) = 2
phi(6) = 6 * (1 - 1 / 2) = 3 phi(8) = 8 * (1 - 1 / 2) = 4
3开始,phi(3) = 3 * (1 - 1 / 3) = 2 phi(6) = 3 * (1 - 1 / 3) = 2
4不是素数,跳过。
5开始,phi(5) = 5 * (1 - 1 / 5) = 4
6不是素数,跳过。
7开始,phi(7) = 7 * (1 - 1 / 7) = 6
8不是素数,跳过。
phi(1) = 1
phi(2) = 1
phi(3) = 2
phi(4) = 2
phi(5) = 4
phi(6) = 2
phi(7) = 6
phi(8) = 4
输出结果1~8 的欧拉函数值的和为22

#include<iostream>
#define Maxn 3000005
using namespace std;
int n,m,i,eul[Maxn];
long long sum;
void getphi(int n)
{
    for(int i = 1; i <= Maxn; i++)
        eul[i] = i;
    for(int i = 2; i <= Maxn; i++)
        if(i == eul[i])
            for(int j = i; j <= Maxn; j+=i)
                eul[j] = (eul[j] / i) * (i - 1);
}
int main()
{
    getphi(Maxn);
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(i = n,sum = 0; i <= m; i++)
            sum += eul[i];
        printf("%lld\n",sum);
    }
    return 0;
}

推荐阅读更多精彩内容