# 欧拉函数
欧拉函数:数字 1~n
中与 n 互质
的数的 个数
,记为φ(n)。
互质的数:公约数只有 1
的两个数。
例如:φ(12)=4,其中 1~12
中共有 4
个与 12
互质的数,分别是 1,5,7,11
。
# 如何求欧拉函数?
# 一个重要性质
这里我们依赖欧拉函数的一个重要性质欧拉函数是一个积性函数,也就是当 n
与 m
互质时,公式φ(nm)=φ(n)∗φ(m) 成立。
# 求解方案
首先,对 n 进行质因数分解。
记为如下公式:n=p1a1∗p2a2∗...∗pkak。
其中每一项都与其他项互质,根据积性函数的性质,上面公式还可以写成:φ(n)=φ(p1a1)∗φ(p2a2)∗...∗φ(pkak) 。
例如:12=22∗3,那么φ(12)=φ(22)∗φ(3)
此时,问题转换为求φ(pkak)。
1~pkak 的中共有pkak 个数字,因为pk 是 质数
,所以与pkak 不互质
的数就是pk 的倍数 (因为他们的公约数除了 1
之外,一定还有pk 本身),也就是pk,2pk,3pk,...pkak−1∗pk,总共有pkak−1 个。
例如:pk=3,ak=5,此时共有 (2*3,3*3,4*3, ... ,3^4 * 3)
是与 3 不互质的数。
所以,用总数pkak 去掉其中非互质的个数pkak−1,剩下的就是与pkak 互质的。也就是 $\varphi (p_k})=p_k}-p_k}}=p_k}*(1-\frac{1}{p_k}) $。
最后,我们整理一下公式:
φ(n)=φ(p1a1)∗φ(p2a2)∗...∗φ(pkak)
=φ(p1a1)∗φ(p2a2)∗...∗φ(pkak)
=(p1a1∗(1−p11))∗(p2a2∗(1−p21))∗...∗(pkak∗(1−pk1))
=p1a1∗p2a2∗...∗pkak∗(1−p11)∗(1−p21)∗...∗(1−pk1)
=n∗(1−p11)∗(1−p21)∗...∗(1−pk1)
=n∗∏ki=1(1−pi1)
# Code
注意,在公式中存在(1−pk1),这种情况如果直接计算,要计算小数,也就是pk1 这里,因此,我们把这里进行一下等价变换:n∗(1−pi1)⇒n÷pi∗(pi−1) 。
| def find(n): |
| i = 2 |
| res = n |
| |
| while i <= n // i: |
| if n % i == 0: |
| while n % i == 0: |
| n //= i |
| |
| res = res // i * (i - 1) |
| i += 1 |
| if n > 1: |
| res = res // n * (n - 1) |
| return res |
# 筛法求欧拉函数
问题升级:要求出 1~n 中所有数字的欧拉函数。
此时,我们发现如果还用上面的方法求 1~n 中每个数字的欧拉函数,那么每个数都要分解质因数,时间复杂度会来到O(nn) 。
那么,回想一下在上一篇文章中,我们 求1~n中的质数
时候是怎么做的呢?
我们使用了一个线性筛法,在O(n) 的时间复杂度下就求出来了 1~n 中的质数。
而在求欧拉函数的时候,我们也要分解质因数,因此可以在求解质数的时候顺便把欧拉函数也求出来。
首先复习一下,质数的线性筛是怎么做的。
| def get_primes(n): |
| cnt = 0 |
| for i in range(2, n + 1): |
| if not st[i]: |
| p[cnt] = i |
| cnt += 1 |
| |
| j = 0 |
| while p[j] <= n // i: |
| st[p[j] * i] = 1 |
| |
| if i % p[j] == 0: |
| break |
| j += 1 |
| return cnt |
从代码中可以比较容易的分析到,筛选过程总共分为两部分,一部分是质数部分,另一部分是筛掉的合数部分。
# 质数部分
在质数部分,求质数的欧拉函数是非常简单的。从定义出发很容易想到 质数n
的欧拉函数就是φ(n)=n−1。
因为 质数的因数只有1和它本身
,而欧拉函数的定义是 1~n中与n互质的个数
,那么除了 n 之外,1~n-1 中所有数都与 n 只有 1
这一个共同因数,所以它们都与 n 互质,总个数就是 n-1 个。
用上小节的求欧拉函数的公式表示就是:φ(n)=n∗(1−n1)=n−1 。
# 合数部分
当触发中止条件 i % p[j] == 0
时,说明 pj 是 i 的质因子。
从上节欧拉函数的推导过程中,我们发现一个数的欧拉函数计算过程和其质因子的指数是没关系的,只与质因子本身有关。
例如:当 n=6
时候,分解质因数结果是2∗3,那么求欧拉值:φ(6)=6∗(1−21)∗(1−31)=2。
当 n=12
时候,分解质因数结果是22∗3,因此,它求欧拉值是φ(12)=12∗(1−21)∗(1−31)=4。
举这个例子,是想说明上面的结论。即,求欧拉值的过程中, 6
和 12
都有相同的质因数 2
和 3
。虽然 12
的 质因子2
的指数是 2
,而 6
的 质因子2
的指数是 1
,但这并没有影响(1−21)∗(1−31) 这部分。
所以,当φ(i∗pj) 中,如果pj 是 i
的质因子之一,那么φ(pj)=pj∗(1−pj1) 中的(1−pj1) 已经在φ(i) 的推导过程中被计算过了,所以,我们得到最终公式:φ(i∗pj)=pj∗i∗(1−p11)∗...∗(1−pj1)∗...∗(1−pk1)=pj∗φ(i)。
当不满足终止条件,也就是 i % pj != 0
,此时pj 和 i
是互质的,那么根据积性函数的性质,可以得到φ(pj∗i)=φ(pj)∗φ(i)。 因为pj 是质数,所以φ(pj)=pj−1。最后,得到最终公式:φ(pj∗i)=(pj−1)∗φ(i)。
# Code
| N = 1000010 |
| st = [0] * N |
| p = [0] * N |
| def find(n): |
| phi = [0] * N |
| cnt = 0 |
| for i in range(2, n + 1): |
| if not st[i]: |
| p[cnt] = i |
| phi[i] = i - 1 |
| cnt += 1 |
| j = 0 |
| while p[j] <= n // i: |
| t = i * p[j] |
| st[t] = 1 |
| if i % p[j] == 0: |
| phi[t] = phi[i] * p[j] |
| break |
| phi[t] = phi[i] * (p[j] - 1) |
| j += 1 |
| res = 0 |
| |
| |
| phi[1] = 1 |
| for i in range(1, n + 1): |
| res += phi[i] |
| return res |