探索数论:欧拉函数的原理

欧拉函数(Euler’s Totient Function),记作 ϕ ( n ) \phi(n) ϕ(n),是数论中的一个重要函数,用于计算小于或等于正整数 n n n 且与 n n n 互质的正整数的个数。换句话说,欧拉函数 ϕ ( n ) \phi(n) ϕ(n) 给出了在 1 1 1 n n n 之间与 n n n 互质的整数的数量。

欧拉函数的性质

  1. 对于质数 p p p

    • 如果 p p p 是一个质数,那么 ϕ ( p ) = p − 1 \phi(p) = p - 1 ϕ(p)=p1,因为 1 1 1 p − 1 p-1 p1 的所有整数都与 p p p 互质。
  2. 对于质数的幂 p k p^k pk

    • 如果 p p p 是一个质数,且 k k k 是一个正整数,那么 ϕ ( p k ) = p k − p k − 1 \phi(p^k) = p^k - p^{k-1} ϕ(pk)=pkpk1。这是因为在 1 1 1 p k p^k pk 之间,只有 p p p 的倍数不与 p k p^k pk 互质,而 p p p 的倍数有 p k − 1 p^{k-1} pk1 个。
  3. 对于两个互质的整数 m m m n n n

    • 如果 m m m n n n 互质,即 gcd ⁡ ( m , n ) = 1 \gcd(m, n) = 1 gcd(m,n)=1,那么 ϕ ( m × n ) = ϕ ( m ) × ϕ ( n ) \phi(m \times n) = \phi(m) \times \phi(n) ϕ(m×n)=ϕ(m)×ϕ(n)。这个性质称为欧拉函数的积性。
  4. 对于一般的正整数 n n n

    • 如果 n n n 的质因数分解为 n = p 1 k 1 × p 2 k 2 × ⋯ × p m k m n = p_1^{k_1} \times p_2^{k_2} \times \cdots \times p_m^{k_m} n=p1k1×p2k2××pmkm,那么欧拉函数可以表示为:
      ϕ ( n ) = n × ( 1 − 1 p 1 ) × ( 1 − 1 p 2 ) × ⋯ × ( 1 − 1 p m ) \phi(n) = n \times \left(1 - \frac{1}{p_1}\right) \times \left(1 - \frac{1}{p_2}\right) \times \cdots \times \left(1 - \frac{1}{p_m}\right) ϕ(n)=n×(1p11)×(1p21)××(1pm1)

第四个性质可以这样来理解,我们取一种简单的情况: n = p 1 k 1 × p 2 k 2 n = p_1^{k_1} \times p_2^{k_2} n=p1k1×p2k2。质因数分解之后,首先去除 p 1 p_1 p1的倍数,一共有 ⌊ n p 1 ⌋ \left\lfloor \frac{n}{p_1}\right\rfloor p1n个,同理去除 p 2 p_2 p2的倍数,一共有 ⌊ n p 2 ⌋ \left\lfloor \frac{n}{p_2}\right\rfloor p2n个。再加上去除了两次的 p 1 , p 2 p_1,p_2 p1,p2的公倍数,有 ⌊ n p 1 × p 2 ⌋ \left\lfloor \frac{n}{p_1\times p_2}\right\rfloor p1×p2n个。因此 ϕ ( n ) = n − ⌊ n p 1 ⌋ − ⌊ n p 2 ⌋ + ⌊ n p 1 × p 2 ⌋ \phi (n) = n - \left\lfloor \frac{n}{p_1}\right\rfloor - \left\lfloor \frac{n}{p_2}\right\rfloor + \left\lfloor \frac{n}{p_1\times p_2}\right\rfloor ϕ(n)=np1np2n+p1×p2n,而 n n n是可以被 p 1 , p 2 p_1,p_2 p1,p2整除的(因为是 n n n的质因数),所以化简得到
ϕ ( n ) = n × ( 1 − 1 p 1 − 1 p 2 + 1 p 1 × p 2 ) = n × ( 1 − 1 p 1 ) × ( 1 − 1 p 2 ) \phi(n) = n \times (1 - \frac{1}{p_1} - \frac{1}{p_2} + \frac{1}{p_1\times p_2})=n\times(1-\frac{1}{p_1})\times(1-\frac{1}{p_2}) ϕ(n)=n×(1p11p21+p1×p21)=n×(1p11)×(1p21)
面对更加多质因数的情况,也是依据容斥原理进行推导,这里就不详细介绍了。

欧拉函数的计算示例

  1. 计算 ϕ ( 9 ) \phi(9) ϕ(9)

    • 9 = 3 2 9 = 3^2 9=32,所以 ϕ ( 9 ) = 9 − 3 = 6 \phi(9) = 9 - 3 = 6 ϕ(9)=93=6
    • 与 9 互质的数有 1, 2, 4, 5, 7, 8,共 6 个。
  2. 计算 ϕ ( 12 ) \phi(12) ϕ(12)

    • 12 = 2 2 × 3 12 = 2^2 \times 3 12=22×3,所以:
      ϕ ( 12 ) = 12 × ( 1 − 1 2 ) × ( 1 − 1 3 ) = 12 × 1 2 × 2 3 = 4 \phi(12) = 12 \times \left(1 - \frac{1}{2}\right) \times \left(1 - \frac{1}{3}\right) = 12 \times \frac{1}{2} \times \frac{2}{3} = 4 ϕ(12)=12×(121)×(131)=12×21×32=4
    • 与 12 互质的数有 1, 5, 7, 11,共 4 个。
  3. 计算 ϕ ( 15 ) \phi(15) ϕ(15)

    • 15 = 3 × 5 15 = 3 \times 5 15=3×5,且 3 和 5 互质,所以:
      ϕ ( 15 ) = ϕ ( 3 ) × ϕ ( 5 ) = ( 3 − 1 ) × ( 5 − 1 ) = 2 × 4 = 8 \phi(15) = \phi(3) \times \phi(5) = (3 - 1) \times (5 - 1) = 2 \times 4 = 8 ϕ(15)=ϕ(3)×ϕ(5)=(31)×(51)=2×4=8
    • 与 15 互质的数有 1, 2, 4, 7, 8, 11, 13, 14,共 8 个。

练习

欧拉函数

ACWing 欧拉函数
给定 n n n个正整数 a i a_i ai,出每个数的欧拉函数。需要注意的是,在利用公式计算欧拉函数,需要先除后乘,这是为了防止溢出

#include <iostream>
#include <vector>
using namespace std;

/**
 * 分解整数x的质因数
 * @param number 需要分解质因数的整数
 * @return 返回一个包含所有质因数的向量
 */
vector<int> decomposeIntoPrimeFactors(int number) {
    vector<int> primeFactors;
    // 从最小的质数2开始,尝试找到所有的质因数
    for (int divisor = 2; divisor <= number / divisor; ++divisor) {
        if (number % divisor == 0) {
            primeFactors.push_back(divisor);  // 将质因数加入结果向量
        }
        // 除以当前质因数直到不能整除为止
        while (number % divisor == 0) {
            number /= divisor;
        }
    }
    // 如果剩余的number大于1,则它本身也是一个质因数
    if (number > 1) {
        primeFactors.push_back(number);
    }
    return primeFactors;
}

/**
 * 计算欧拉函数phi(x),即小于x且与x互质的正整数的数量
 * @param number 输入整数
 * @return 返回phi(x)的值
 */
int calculateEulerTotient(int number) {
    auto primeFactors = decomposeIntoPrimeFactors(number);
    int totientValue = number;
    // 根据公式phi(x) = x * (1 - 1/p1) * (1 - 1/p2) ... (1 - 1/pn)
    for (int factor : primeFactors) {
        totientValue = totientValue / factor * (factor - 1);
    }
    return totientValue;
}

int main() {
    ios::sync_with_stdio(false);  // 提高I/O效率
    cin.tie(0);
    cout.tie(0);

    int testCases;
    cin >> testCases;  // 输入测试案例的数量

    while (testCases--) {
        int number;
        cin >> number;  // 输入每个测试案例的数字
        
        // 计算并输出该数字的欧拉函数值
        int eulerTotientValue = calculateEulerTotient(number);
        cout << eulerTotientValue << endl;
    }

    return 0;
}

筛法求欧拉函数

ACWing 筛法求欧拉函数
给定一个正整数 n n n,求 1 ∼ n 1∼n 1n 中每个数的欧拉函数之和。为了正确计算从 1 1 1 n n n的所有整数的欧拉函数之和,可以使用线性筛法来同时计算每个数的欧拉函数值并累加它们。以下是详细的步骤:

  1. 线性筛法:利用埃拉托斯特尼筛法或线性筛法来标记质数,并在此过程中计算每个数的欧拉函数值。
  2. 计算欧拉函数:根据欧拉函数的定义,如果一个数 n 的质因数分解为 p 1 k 1 × p 2 k 2 × … × p m k m p_1^{k_1} \times p_2^{k_2} \times \ldots \times p_m^{k_m} p1k1×p2k2××pmkm,则 φ(n) 可以表示为:
    ϕ ( n ) = n × ( 1 − 1 p 1 ) × ( 1 − 1 p 2 ) × … × ( 1 − 1 p m ) \phi(n) = n \times (1 - \frac{1}{p_1}) \times (1 - \frac{1}{p_2}) \times \ldots \times (1 - \frac{1}{p_m}) ϕ(n)=n×(1p11)×(1p21)××(1pm1)

接着,我们来看代码种应当如何实现。首先,让我们会议一下线性筛法:

void linearSieve(int n){
	memset(isPrime, true, sizeof isPrime);
	isPrime[0] = isPrime[1] = false;
	for(int i=2;i<=n;i++){
		if(isPrime[i]){
			prime[primeCount++] = i;
		}
		for(int j = 0;j<primeCount && i * prime[j] <= n;j++){
			isPrime[i*prime[j]] = false;
			if(i % prime[j] == 0)
				break;
		}
	}
}

接着,我们很容易可以想出,在判断数字是质数的时候,可以将其欧拉函数值设置为i - 1。另外的核心就是在向后筛质数的时候,变量i充当的是一个倍数的作用,这也是线性筛法确保每个合数只被遍历一遍的原因。我们又知道,欧拉函数值是具有可积性的。那么,当我筛到 p r i m e [ j ] prime[j] prime[j]的倍数的时候,倍数的欧拉函数则可以用 ϕ ( i ) ∗ ϕ ( p r i m e [ j ] ) \phi (i) * \phi (prime[j]) ϕ(i)ϕ(prime[j])来计算其欧拉函数值。但是,可积性的前提是互质,也就是说,当i % prime[j] != 0,我们才可以用 ϕ ( i ) ∗ ϕ ( p r i m e [ j ] ) \phi (i) * \phi (prime[j]) ϕ(i)ϕ(prime[j])来更新 ϕ ( i ∗ p r i m e [ j ] ) \phi (i * prime[j]) ϕ(iprime[j])。而对于i % prime[j] == 0时,说明 p r i m e [ j ] prime[j] prime[j]也是 i i i的一个质因子,而欧拉函数公式是 ϕ ( n ) = n × ( 1 − 1 p 1 ) × ( 1 − 1 p 2 ) × … × ( 1 − 1 p m ) \phi(n) = n \times (1 - \frac{1}{p_1}) \times (1 - \frac{1}{p_2}) \times \ldots \times (1 - \frac{1}{p_m}) ϕ(n)=n×(1p11)×(1p21)××(1pm1)
1 − 1 p r i m e [ j ] 1 - \frac{1}{prime[j]} 1prime[j]1这个因子显然在 ϕ ( i ) \phi(i) ϕ(i)中计算过了,我们只需要将 ϕ ( i ) \phi (i) ϕ(i)的基数 i i i扩大 p r i m e [ j ] prime[j] prime[j]倍即可。以下是一个使用线性筛法高效计算从 1 1 1 n n n的所有整数的欧拉函数之和的实现:

#include <iostream>
#include <cstring>
using namespace std;

const int maxn = 1e6 + 7;
int phi[maxn];  // 存储每个数的欧拉函数值
bool isPrime[maxn];  // 标记是否为质数
int prime[maxn];  // 存储质数
int primeCount = 0;  // 质数的数量

void linearSieveAndEulerPhiSum(int n) {
    memset(isPrime, true, sizeof(isPrime));  // 初始化所有数为质数
    long long sum = 1;  // 用于存储欧拉函数之和,值为1的欧拉函数值

    for (int i = 2; i <= n; ++i) {
        if (isPrime[i]) {
            prime[primeCount++] = i;
            phi[i] = i - 1;  // 如果i是质数,则φ(i) = i - 1
        }
        for (int j = 0; j < primeCount && i * prime[j] <= n; ++j) {
            isPrime[i * prime[j]] = false;
            if (i % prime[j] == 0) {
                phi[i * prime[j]] = phi[i] * prime[j];
                break;
            } else {
                phi[i * prime[j]] = phi[i] * (prime[j] - 1);
            }
        }
        sum += phi[i];
    }

    cout << sum << endl;
}

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

    int n;
    cin >> n;

    linearSieveAndEulerPhiSum(n);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值