【数论】欧拉函数
文章目录
- 上文链接
- 一、欧拉函数
- 1. 欧拉函数的定义
- 2. 欧拉函数的性质
- 3. 欧拉函数的求法
- 4. 代码求解单个数的欧拉函数
- 5. 欧拉筛打表欧拉函数
- 二、OJ 练习
- 1. 仪仗队 ⭐⭐⭐
- (1) 解题思路
- (2) 代码实现
- 2. GCD ⭐⭐⭐⭐
- (1) 解题思路
- (2) 代码实现
上文链接
- 质数筛(埃氏筛、欧拉筛)
一、欧拉函数
1. 欧拉函数的定义
欧拉函数:对于一个正整数 m m m,若小于 m m m 的正整数中与 n n n 互素的数(包括 1 1 1)的个数为 s s s,并定义 φ ( m ) = s \varphi(m)=s φ(m)=s, φ ( m ) \varphi(m) φ(m) 称为欧拉函数。
2. 欧拉函数的性质
- 性质 1:若 a a a 和 b b b 互素,则
φ ( a b ) = φ ( a ) φ ( b ) (1) φ(ab) = φ(a) φ(b)\tag 1 φ(ab)=φ(a)φ(b)(1)
- 性质 2:若 m , n m,n m,n 满足 m ∣ n m\mid n m∣n,则
φ ( m ) ∣ φ ( n ) (2) \varphi(m)\mid\varphi(n)\tag 2 φ(m)∣φ(n)(2)
- 性质 3: 如果 p p p 是一个素数,则
φ ( p ) = p − 1 (3) φ(p) = p - 1\tag 3 φ(p)=p−1(3)
- 性质 4: 如果 p p p 是素数, k k k 是正整数,则
φ ( p k ) = p k − p k − 1 = p k − 1 ( p − 1 ) (4) φ(p^k) = p^k - p^{k-1} = p^{k-1}(p - 1)\tag4 φ(pk)=pk−pk−1=pk−1(p−1)(4)
证明:
设 φ ( n ) = φ ( p k ) φ(n)=φ(p^k) φ(n)=φ(pk),即 n = p k n=p^k n=pk。
那么由于 p p p 是一个素数,所以只要一个数的素因数里不包含 p p p,那么这个数就能与 n n n 互素。反之只要含 p p p 这个素因数,那么就不和 n n n 互素。也就是说像 p , 2 p , ⋯ p k − 1 p p,2p,\cdots p^{k-1}p p,2p,⋯pk−1p 这些数都不与 n = p k n=p^k n=pk 互素。那么与 n n n 互素的就有 p k − p k − 1 p^k-p^{k-1} pk−pk−1 个了,即
φ ( n ) = φ ( p k ) = p k − p k − 1 = p k − 1 ( p − 1 ) φ(n)=φ(p^k)= p^k - p^{k-1} = p^{k-1}(p - 1) φ(n)=φ(pk)=pk−pk−1=pk−1(p−1)
- ==性质 5:==由算数基本定理,对于 n n n 的质因数分解为 n = p 1 k 1 p 2 k 2 ⋯ p m k m n = p_1^{k_1} p_2^{k_2} \cdots p_m^{k_m} n=p1k1p2k2⋯pmkm,则
φ ( n ) = n ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) ⋯ ( 1 − 1 p m ) (5) φ(n) = n \left(1 - \frac{1}{p_1}\right) \left(1 - \frac{1}{p_2}\right) \cdots \left(1 - \frac{1}{p_m}\right)\tag5 φ(n)=n(1−p11)(1−p21)⋯(1−pm1)(5)证明:利用性质 1, φ ( n ) φ(n) φ(n) 可以表示为各素因数的积,即
φ ( n ) = φ ( p 1 k 1 ) ⋅ φ ( p 2 k 2 ) ⋯ φ ( p m k m ) φ(n) = φ(p_1^{k_1}) \cdot φ(p_2^{k_2}) \cdots φ(p_m^{k_m}) φ(n)=φ(p1k1)⋅φ(p2k2)⋯φ(pmkm)
能这样拆分是因为 p 1 , p 2 , ⋯ p m p_1,p_2,\cdots p_m p1,p2,⋯pm 都是不同的素数,因此无论它们乘了多少次方,又或者把它们乘方之后拿其中几个相乘,它们之间一定不会有相同的因数的。换句话说, p 1 k 1 , p 2 k 2 , ⋯ p m k m p_1^{k_1},p_2^{k_2},\cdots p_m^{k_m} p1k1,p2k2,⋯pmkm 这些数两两之间都是互素的。因此满足性质 1 的条件——“若 a a a 和 b b b 互素”。于是,我们根据性质 4 进一步展开为:
φ ( n ) = p 1 k 1 ( 1 − 1 p 1 ) ⋅ p 2 k 2 ( 1 − 1 p 2 ) ⋯ p m k m ( 1 − 1 p m ) \varphi (n)=p_1^{k_1} \left(1 - \frac{1}{p_1}\right) \cdot p_2^{k_2} \left(1 - \frac{1}{p_2}\right) \cdots p_m^{k_m} \left(1 - \frac{1}{p_m}\right) φ(n)=p1k1(1−p11)⋅p2k2(1−p21)⋯pmkm(1−pm1)把括号外的所有 p i k i p_i^{k_i} piki 合并起来,会发现 p 1 k 1 p 2 k 2 ⋯ p m k m p_1^{k_1} p_2^{k_2} \cdots p_m^{k_m} p1k1p2k2⋯pmkm 就是 n n n。于是
φ ( n ) = n ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) ⋯ ( 1 − 1 p m ) φ(n) = n \left(1 - \frac{1}{p_1}\right) \left(1 - \frac{1}{p_2}\right) \cdots \left(1 - \frac{1}{p_m}\right) φ(n)=n(1−p11)(1−p21)⋯(1−pm1)
证毕!
3. 欧拉函数的求法
- 根据定义进行求解
例如,我们求解 φ ( 10 ) \varphi(10) φ(10),可以把所有小于10的正整数全部列出来: { 1 , 2 , ⋯ 9 , 10 } \{1,2,\cdots9,10\} {1,2,⋯9,10},再去掉与10不互素的数,最后 φ ( 10 ) = 4 \varphi(10)=4 φ(10)=4。
当然这个方法确实挺万能的,前提是你有时间…
- 根据性质 3 进行求解
若 m m m 是一个素数,可以直接根据上面提到的(3)式进行求解
例如, φ ( 7 ) = 7 − 1 = 6 \varphi(7)=7-1=6 φ(7)=7−1=6。
- 性质 1 + 性质 3
若 m m m 不是一个素数,我们可以根据性质 1 将其拆分为两个欧拉函数的乘积的形式,再通过性质 3,也就是(3)式进行求解
例如, φ ( 66 ) = φ ( 6 ) φ ( 11 ) = φ ( 2 ) φ ( 3 ) φ ( 11 ) = ( 2 − 1 ) × ( 3 − 1 ) × ( 11 − 1 ) = 20 \varphi(66)=\varphi(6)\varphi(11)=\varphi(2)\varphi(3)\varphi(11)=(2-1)\times(3-1)\times(11-1)=20 φ(66)=φ(6)φ(11)=φ(2)φ(3)φ(11)=(2−1)×(3−1)×(11−1)=20。(注意拆出的两个数一定要是互素的,否则不能这样拆)
- 用通式进行求解
当然,欧拉函数的求法有一个通式,也就是我们上面提到的(5)式:
φ ( n ) = n ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) ⋯ ( 1 − 1 p m ) φ(n) = n \left(1 - \frac{1}{p_1}\right) \left(1 - \frac{1}{p_2}\right) \cdots \left(1 - \frac{1}{p_m}\right) φ(n)=n(1−p11)(1−p21)⋯(1−pm1)
4. 代码求解单个数的欧拉函数
只求解一个数的欧拉函数,我们可以通过用通式进行求解。
int phi(int n)
{int ret = n;for(int i = 2; i <= n / i; i++){// 如果 i 是 n 的质因数if(n % i == 0){ret = ret / i * (i - 1); // 先除后乘,保证不会溢出 while(n % i == 0) n /= i;}}// 别忘记判断最后⼀个数if(n > 1) ret = ret / n * (n - 1);return ret;
}
时间复杂度为: O ( n ) O(\sqrt{n}) O(n)。
5. 欧拉筛打表欧拉函数
如果现在求解 [ 1 , n ] [1, n] [1,n] 中所有数的欧拉函数,那么每个数都用通式求解,时间复杂度会比较高。我们可以对筛质数时用到的欧拉筛进行改造,来实现求解 [ 1 , n ] [1, n] [1,n] 中所有数的欧拉函数。这样我们的时间复杂度就能降至 O ( n ) O(n) O(n)。
在线性筛的过程中:
如果该数是质数,那么 φ ( i ) = i − 1 \varphi(i) = i - 1 φ(i)=i−1;
如果该数是合数,每个合数 x x x 都是被它的最小质因数筛掉的。
设 p j p_j pj 是 x x x 的最小质因数,则 x x x 是通过 p j × i p_j\times i pj×i 筛掉的,也就是 x = p j × i x = p_j \times i x=pj×i;
如果 p j p_j pj 是 i i i 的因数,那么 i i i 这个数就包含了 x x x 的所有质因子,由欧拉函数的计算公式得:
φ ( x ) = x ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) ⋯ ( 1 − 1 p m ) = p j × i × ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) ⋯ ( 1 − 1 p m ) = p j × φ ( i ) \begin{aligned} φ(x) &= x \left(1 - \frac{1}{p_1}\right) \left(1 - \frac{1}{p_2}\right) \cdots \left(1 - \frac{1}{p_m}\right) \\ &= p_j \times i\times \left(1 - \frac{1}{p_1}\right) \left(1 - \frac{1}{p_2}\right) \cdots \left(1 - \frac{1}{p_m}\right) \\ &= p_j \times \varphi(i) \end{aligned} φ(x)=x(1−p11)(1−p21)⋯(1−pm1)=pj×i×(1−p11)(1−p21)⋯(1−pm1)=pj×φ(i)如果 p j p_j pj 不是 i i i 的因数,那么 i i i 和 p j p_j pj 必定互质,则
φ ( x ) = φ ( p j × i ) = φ ( p j ) × φ ( i ) = ( p j − 1 ) × φ ( i ) \begin{aligned} \varphi(x) &= \varphi(p_j\times i) \\ &= \varphi(p_j) \times \varphi(i) \\ &= (p_j - 1) \times \varphi(i) \end{aligned} φ(x)=φ(pj×i)=φ(pj)×φ(i)=(pj−1)×φ(i)因此,我们可以从前往后用欧拉筛把所有数的欧拉函数求出来。
代码实现:
int p[N], phi[N]; // 分别记录 1-n 中的质数和 1-n 中所有数的欧拉函数
bool vis[N]; // 检查某个数是否被标记为合数
int cnt; // 记录质数的个数// 线性筛 + 打表欧拉函数
void get_phi(int n)
{// 1 这个数单独处理phi[1] = 1;// 从 2 开始枚举每一个数for(int i = 2; i <= n; i++){// 如果这个数 i 没有被标记,也就是说 i 是质数if(!vis[i]){p[cnt++] = i; // 记录这个质数phi[i] = i - 1; // 质数的欧拉函数就是 i - 1}// 标记合数 + 计算欧拉函数for(int j = 0; i * p[j] <= n; j++){int x = i * p[j];vis[x] = true;if(i % p[j] == 0) // 如果 p[j] 是 i 的因数{phi[x] = phi[i] * p[j]; // 由上面的推导可得break;}else // i 和 p[j] 互质{phi[x] = phi[i] * (p[j] - 1); // 由上面的推导可得}}}
}
二、OJ 练习
1. 仪仗队 ⭐⭐⭐
【题目链接】
[P2158 SDOI2008] 仪仗队 - 洛谷
【题目描述】
作为体育委员,C 君负责这次运动会仪仗队的训练。仪仗队是由学生组成的 N × N N \times N N×N 的方阵,为了保证队伍在行进中整齐划一,C 君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图)。
现在,C 君希望你告诉他队伍整齐时能看到的学生人数。
【输入格式】
一行,一个正整数 N N N。
【输出格式】
输出一行一个数,即 C 君应看到的学生人数。
【示例一】
输入
4输出
9
【说明/提示】
对于 100 % 100 \% 100% 的数据, 1 ≤ N ≤ 40000 1 \le N \le 40000 1≤N≤40000。
(1) 解题思路
这个问题可以稍加简化一下,由于左上和右下部分能看得见的位置数量一定是完全对称的,所以我们只需考虑这张图的一半即可,像下面标为 1 的位置。
0 0 0 0
0 0 0 1
0 0 1 1
0 1 1 1
以左下角的人为原点建立一个直角坐标系,不难发现,一个位置 ( x , y ) (x, y) (x,y) 如果想要不被挡住,那么它的坐标一定要满足 g c d ( x , y ) = 1 gcd(x, y) = 1 gcd(x,y)=1,也就是横坐标和纵坐标互质。因为如果它们不互质,那么它就一定会被 ( x / g c d ( x , y ) , y / g c d ( x , y ) ) (x/gcd(x, y),\ y/gcd(x, y)) (x/gcd(x,y), y/gcd(x,y)) 挡住。现在我们只需要求解在右下半区域横纵坐标互质的位置个数即可。
在这个区域中, x > y x > y x>y,如果 x x x 固定,想要求解一个 y y y 与 x x x 互质,那么只能从 [ 1 , x − 1 ] [1, x - 1] [1,x−1] 中寻找,现在有点感觉了吗?这不就是在求 x x x 的欧拉函数吗!对于每一个 x x x 与它互质的 y y y 的个数就是 x x x 的欧拉函数 φ ( x ) \varphi(x) φ(x)。所以我们只需要对于每一个 x x x 都求一下它的欧拉函数 φ ( x ) \varphi(x) φ(x),然后把它们加起来得到 n n n,那么右下区域能被看到的位置的个数就是 n n n。那么左上半区域能被看到的位置个数也为 n n n,最后再加上对角线上能被看到的一个位置,最终结果就是 2 n + 1 2n + 1 2n+1。
(2) 代码实现
#include<iostream>using namespace std;const int N = 4e4 + 10;
int p[N], phi[N];
bool vis[N];
int cnt;// 把 1-n 中所有的欧拉函数都求出来放到 phi 数组
void get_phi(int n)
{phi[1] = 1; // 1 单独处理for(int i = 2; i <= n; i++){// i 是质数if(!vis[i]){p[cnt++] = i;phi[i] = i - 1; // 质数的欧拉函数是 i - 1}for(int j = 0; i * p[j] <= n; j++){int x = i * p[j];vis[x] = true;if(i % p[j] == 0){phi[x] = phi[i] * p[j];break;}else // i 和 p[j] 互质{phi[x] = phi[i] * (p[j] - 1);}}}
}int main()
{int n;cin >> n;// 边长为 n, 但坐标从 0 开始,因此只需求到 n-1get_phi(n - 1);int sum = 0;for(int i = 1; i < n; i++) sum += phi[i];if(n == 1) cout << 0 << endl;else cout << (sum * 2 + 1) << endl;return 0;
}
2. GCD ⭐⭐⭐⭐
【题目链接】
P2568 GCD - 洛谷
【题目描述】
给定正整数 n n n,求 1 ≤ x , y ≤ n 1\le x,y\le n 1≤x,y≤n 且 gcd ( x , y ) \gcd(x,y) gcd(x,y) 为素数的数对 ( x , y ) (x,y) (x,y) 有多少对。
【输入格式】
只有一行一个整数,代表 n n n。
【输出格式】
一行一个整数表示答案。
【示例一】
输入
4输出
4
【说明/提示】
样例输入输出 1 解释
对于样例,满足条件的 ( x , y ) (x,y) (x,y) 为 ( 2 , 2 ) (2,2) (2,2), ( 2 , 4 ) (2,4) (2,4), ( 3 , 3 ) (3,3) (3,3), ( 4 , 2 ) (4,2) (4,2)。
数据规模与约定
- 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 1 0 7 1\le n\le10^7 1≤n≤107。
(1) 解题思路
假设 g c d ( x , y ) = d gcd(x, y) = d gcd(x,y)=d,则 x x x 和 y y y 一定是 d d d 的倍数,那么我们要找 x x x 和 y y y 必定是从 { d , 2 d , 3 d , ⋯ , ⌊ n d ⌋ d d, 2d, 3d, \cdots, \lfloor\frac{n}{d}\rfloor d d,2d,3d,⋯,⌊dn⌋d } 这些数中寻找。现在假设 x = a d , y = b d x = ad,\ y = bd x=ad, y=bd,则 g c d ( x , y ) = d × g c d ( a , b ) gcd(x, y) = d \times gcd(a, b) gcd(x,y)=d×gcd(a,b),由于题目要求 g c d ( x , y ) gcd(x, y) gcd(x,y) 必须是质数,也就是说 d d d 要为质数,所以 g c d ( a , b ) gcd(a, b) gcd(a,b) 必须等于 1 1 1,即 a , b a, b a,b 必须互质!
现在我们从小到大枚举质数作为 d d d,对于每一个 d d d,去构造 x , y x, y x,y。假设我构造出了 x = 5 d x = 5d x=5d,那么 y y y 等于多少倍的 d d d 呢?根据上面的认识,这里 d d d 前面的系数必须要与 5 5 5 互质才行,所以 y y y 的取值总共就有 φ ( 5 ) \varphi(5) φ(5) 个。
所以现在我们知道了,首先从小到达枚举质数 d d d,之后用 1 d , 2 d , 3 d , ⋯ 1d, 2d, 3d,\cdots 1d,2d,3d,⋯ 一直到 ⌊ n d ⌋ d \lfloor\frac{n}{d}\rfloor d ⌊dn⌋d 作为 x x x,它们对应的 y y y 的取值个数分别是 φ ( 1 ) , φ ( 2 ) , φ ( 3 ) , ⋯ φ ( ⌊ n d ⌋ ) \varphi(1), \varphi(2), \varphi(3),\cdots \varphi(\lfloor\frac{n}{d}\rfloor) φ(1),φ(2),φ(3),⋯φ(⌊dn⌋),总共 ∑ i = 1 ⌊ n d ⌋ φ ( i ) \sum\limits_{i = 1}^{\lfloor\frac{n}{d}\rfloor} \varphi(i) i=1∑⌊dn⌋φ(i) 个。像这样枚举完之后我们总共会有 ∑ d ∈ p ∑ i = 1 ⌊ n d ⌋ φ ( i ) \sum\limits_{d\in \operatorname{p}}\sum\limits_{i = 1}^{\lfloor\frac{n}{d}\rfloor} \varphi(i) d∈p∑i=1∑⌊dn⌋φ(i) 个取值( 这里 p \operatorname{p} p 表示 [ 1 , n ] [1, n] [1,n] 中的质数)。
由于这道题的 x x x 和 y y y 可以互换位置,所以对于每一个 d d d,我们可以得到的对数一共有 2 [ ∑ i = 1 ⌊ n d ⌋ φ ( i ) ] − 1 2[\sum\limits_{i = 1}^{\lfloor\frac{n}{d}\rfloor} \varphi(i)] - 1 2[i=1∑⌊dn⌋φ(i)]−1 个。因此最终的答案为
∑ d ∈ p { 2 [ ∑ i = 1 ⌊ n d ⌋ φ ( i ) ] − 1 } \sum\limits_{d\in \operatorname{p}}\{2[\sum\limits_{i = 1}^{\lfloor\frac{n}{d}\rfloor} \varphi(i)] - 1\} d∈p∑{2[i=1∑⌊dn⌋φ(i)]−1}
由于我们要多次计算欧拉函数的部分和,为了快速计算它们的和,我们可以在打表完欧拉函数之后求一下前缀和。
(2) 代码实现
#include<iostream>using namespace std;typedef long long LL;const int N = 1e7 + 10;
int p[N], phi[N], cnt;
LL pref[N];
bool vis[N];// 打表欧拉函数
void get_phi(int n)
{phi[1] = 1;for(int i = 2; i <= n; i++){if(!vis[i]){p[cnt++] = i;phi[i] = i - 1;}for(int j = 0; i * p[j] <= n; j++){int x = i * p[j];vis[x] = true;if(i % p[j] == 0){phi[x] = p[j] * phi[i];break;}else{phi[x] = (p[j] - 1) * phi[i];}}}
}// 求欧拉函数表的前缀和
void pre_sum(int n)
{for(int i = 1; i <= n; i++){pref[i] = pref[i - 1] + phi[i];}
}int main()
{int n;cin >> n;get_phi(n);pre_sum(n);LL sum = 0;// 从小到大枚举质数for(int i = 0; i < cnt; i++){sum += pref[n / p[i]] * 2 - 1;}cout << sum << endl;return 0;
}

