欧拉函数
1. 欧拉函数原理
欧拉函数
-
1 ~ N 中与 N 互质的数的个数被称为欧拉函数,记为 ϕ ( N ) \phi(N) ϕ(N),例如 ϕ ( 6 ) = 2 \phi(6)=2 ϕ(6)=2,因为1~6中和6互质的有1和5。
-
特殊规定 ϕ ( 1 ) = 1 \phi(1)=1 ϕ(1)=1。
-
如果一个数N可以质因数分解为 p 1 α 1 ∗ p 2 α 2 ∗ . . . ∗ p k α k p_1^{\alpha_1}*p_2^{\alpha_2}*...*p_k^{\alpha_k} p1α1∗p2α2∗...∗pkαk ,则
ϕ ( N ) = N ∗ p 1 − 1 p 1 ∗ p 2 − 1 p 2 ∗ . . . ∗ ∗ p k − 1 p k \phi(N) = N*{\frac{p_1-1}{p_1}}*{\frac{p_2-1}{p_2}}*...**{\frac{p_k-1}{p_k}} ϕ(N)=N∗p1p1−1∗p2p2−1∗...∗∗pkpk−1 -
使用容斥原理可以证明上述结论:
(1)减去所有在1~N之间的是 p 1 , p 2 , . . . , p k p_1,p_2,...,p_k p1,p2,...,pk 倍数的数据的个数
(2)加上所有在1~N之间的是 p 1 , p 2 , . . . , p k p_1,p_2,...,p_k p1,p2,...,pk 任意两个数据之积倍数的数据的个数
(3)减去所有在1~N之间的是 p 1 , p 2 , . . . , p k p_1,p_2,...,p_k p1,p2,...,pk 任意三个数据之积倍数的数据的个数
(4)…
即:
N − N p 1 − N p 2 − . . . − − N p k + N p 1 p 2 + N p 1 p 3 + . . . + N p k − 1 p k − N p 1 p 2 p 3 − N p 1 p 2 p 4 − . . . − N p k − 2 p k − 1 p k . . . . . . N - \frac{N}{p_1} - \frac{N}{p_2} - ... - - \frac{N}{p_k} \\ +\frac{N}{p_1p_2} + \frac{N}{p_1p_3} + ... + \frac{N}{p_{k-1}p_k} \\ -\frac{N}{p_1p_2p_3} - \frac{N}{p_1p_2p_4} - ... - \frac{N}{p_{k-2}p_{k-1}p_k} \\\\ ...... N−p1N−p2N−...−−pkN+p1p2N+p1p3N+...+pk−1pkN−p1p2p3N−p1p2p4N−...−pk−2pk−1pkN......
这个式子就是 ϕ ( N ) \phi(N) ϕ(N)。 -
该算法的瓶颈是在质因数分解,因此时间复杂度是 O ( N ) O(\sqrt N) O(N)的。
筛法求欧拉函数
-
这里我们想要求解1~N中每个数据的欧拉函数值,如果一个一个求解的话,时间复杂度为 O ( N × N ) O(N \times \sqrt N) O(N×N),很慢,因此这里可以使用筛法求解1~N中每个数据的欧拉函数,时间复杂度是 O ( N ) O(N) O(N)的。
-
在筛质数的线性筛法中加入一些操作,可以解决该问题。其实在线性筛法的过程中可以求出很多内容。
-
具体步骤可以参考如下代码
const int N = 1000010;
int primes[N], cnt; // 存储cnt个质数
int phi[N]; // 每个数的欧拉函数值
bool st[N]; // 是否被筛掉
// 时间复杂度:O(n)
void get_eulers(int n) {
phi[1] = 1;
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
phi[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) {
phi[primes[j] * i] = phi[i] * primes[j];
break;
}
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
}
- 分析如下:
// 时间复杂度:O(n)
void get_eulers(int n) {
phi[1] = 1;
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
phi[i] = i - 1; // i为质数,1~i-1均与其互质
}
for (int j = 0; primes[j] <= n / i; j++) {
// 令primes[j] = pj, 考察数据a = pj * i, b = i
st[primes[j] * i] = true;
if (i % primes[j] == 0) {
// 因为pj是a的质因数,根据欧拉公式,容易推出:phi[a] = phi[b] * pj;
phi[primes[j] * i] = phi[i] * primes[j];
break;
}
// 此时i % pj != 0
// phi[a] = phi[b] * pj * (1 - 1/pj) = phi[b] * (pj - 1);
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
}
欧拉定理
-
同余的性质
(1)性质1:
a≡a(mod m)
;(反身性)(2)性质2:若
a≡b(mod m)
,那么b≡a(mod m)
;(对称性)。(3)性质3:若
a≡b(mod m)
,b≡c(mod m)
,那么a≡c(mod m)
;(传递性)。(4)性质4:若
a≡b(mod m)
,c≡d(mod m)
,那么a±c≡b±d(mod m)
;(可加减性)。(5)性质5:若
a≡b(mod m)
,c≡d(mod m)
,那么ac≡bd(mod m)
;(可乘性)。(6)性质6:若
a≡b(mod m)
,那么 a × n ≡ b × n ( m o d m ) a \times n≡b \times n(mod \ \ m) a×n≡b×n(mod m),(其中n为自然数);(7)性质7:若 a × c ≡ b × c ( m o d m ) , ( c , m ) = 1 a \times c≡b \times c(mod \ \ m),(c,m)=1 a×c≡b×c(mod m),(c,m)=1,那么
a≡b(mod m)
,(记号(c,m)
表示c与m的最大公约数);(8)性质8:若
a≡b(mod m)
,那么a的n次方和b的n次方也对于m同余;(9)性质9:若
a≡b(mod m)、c≡d(mod m)、e≡f(mod m)……x≡y(mod m)
,那么:a+c+e+……+x
和b+d+f+……+y
也对于m同余。 -
欧拉定理:若a与n互质,则有: a ϕ ( n ) ≡ 1 ( m o d n ) a^{\phi(n)} \equiv 1(mod \ n) aϕ(n)≡1(mod n)。
证明:1~n中和n互质的数有 ϕ ( n ) \phi(n) ϕ(n) 个,假设分别是: b 1 , b 2 , . . . , b ϕ ( n ) b_1,b_2,...,b_{\phi(n)} b1,b2,...,bϕ(n),因为a与n互质,则有: a ∗ b 1 , a ∗ b 2 , . . . , a ∗ b ϕ ( n ) a*b_1,a*b_2,...,a*b_{\phi(n)} a∗b1,a∗b2,...,a∗bϕ(n) 均与n互质,并且两两互不相同(反证法,假设存在两个数在模n的前提下是相同的,假设是 a × b i ≡ a × b j ( m o d n ) a \times b _ i ≡ a \times b _j (mod \ \ n) a×bi≡a×bj(mod n),则 a × ( b i − b j ) ≡ 0 ( m o d n ) a \times (b_i - b_j)≡0 (mod \ \ n) a×(bi−bj)≡0(mod n),所以 b i − b j ≡ 0 ( m o d n ) b_i - b_j≡0 (mod \ \ n) bi−bj≡0(mod n),此时推出 b i 、 b j b_i、b_j bi、bj相等,矛盾),所以这两组数是同一组数(在模n的前提下),只不过是位置调换了一下,因此这两组数的乘积同余,所以有:
a ϕ ( n ) × ( b 1 b 2 . . . b ϕ ( n ) ) ≡ ( b 1 b 2 . . . b ϕ ( n ) ) ( m o d n ) a^{\phi(n)}\times(b_1b_2...b_{\phi(n)}) \equiv (b_1b_2...b_{\phi(n)}) (mod \ n) aϕ(n)×(b1b2...bϕ(n))≡(b1b2...bϕ(n))(mod n)
根据同余的性质,有: a ϕ ( n ) ≡ 1 ( m o d n ) a^{\phi(n)} \equiv 1 (mod \ n) aϕ(n)≡1(mod n),证毕! -
欧拉定理的一个特例(费马小定理):若a与p互质,且p是质数,则有: a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1(mod \ p) ap−1≡1(mod p)。
2. AcWing上的欧拉函数题目
AcWing 873. 欧拉函数
问题描述
-
问题链接:AcWing 873. 欧拉函数
分析
-
从定义出发直接求解即可。
-
计算量:主要在质因数分解上,质因数分解的计算量最多是 a i \sqrt {a_i} ai,然后最多100个数,因此计算量在 100 × 2 × 1 0 9 ≈ 4 × 1 0 6 100 \times \sqrt {2 \times 10 ^ 9} \approx 4 \times 10 ^ 6 100×2×109≈4×106,400多万的计算量,完全可以接受。
代码
- C++
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
while (n--) {
int a;
cin >> a;
int res = a;
for (int i = 2; i <= a / i; i++)
if (a % i == 0) {
res = res / i * (i - 1); // 先除再乘是为了防止溢出
while (a % i == 0) a /= i;
}
if (a > 1) res = res / a * (a - 1);
cout << res << endl;
}
return 0;
}
AcWing 874. 筛法求欧拉函数
问题描述
-
问题链接:AcWing 874. 筛法求欧拉函数
分析
- 使用筛法求欧拉函数即可。
代码
- C++
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1000010;
int primes[N], cnt; // 存储cnt个质数
int phi[N]; // 每个数的欧拉函数值
bool st[N]; // 是否被筛掉
LL get_eulers(int n) {
phi[1] = 1;
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
phi[i] = i - 1;
}
for (int j = 0; primes[j] <= n / i; j++) {
st[primes[j] * i] = true;
if (i % primes[j] == 0) {
phi[primes[j] * i] = phi[i] * primes[j];
break;
}
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
LL res = 0;
for (int i = 1; i <= n; i++) res += phi[i];
return res;
}
int main() {
int n;
cin >> n;
cout << get_eulers(n) << endl;
return 0;
}
AcWing 201. 可见的点
问题描述
-
问题链接:AcWing 201. 可见的点
分析
-
首先是结论:点 ( x 0 , y 0 ) (x_0, y_0) (x0,y0)是可见的 ⟺ \iff ⟺ x 0 , y 0 x_0, y_0 x0,y0是互质的。下面证明这个结论。
-
首先是充分性:反证法,如果 ( x 0 , y 0 ) (x_0, y_0) (x0,y0)不是互质的,设其最大公约数为
d
,则d > 1
,因此 ( x 0 d , y 0 d ) (\frac{x_0}{d}, \frac{y_0}{d}) (dx0,dy0)会把 ( x 0 , y 0 ) (x_0, y_0) (x0,y0)遮住(因为斜率相同),充分性成立。 -
必要性:反证法,如果 ( x 0 , y 0 ) (x_0, y_0) (x0,y0)是互质的,但是不可见,则说明一定存在 x < x 0 , y < y 0 x < x_0, y < y_0 x<x0,y<y0使得 y 0 x 0 = = y x \frac{y_0}{x_0} == \frac{y}{x} x0y0==xy,说明可以被约分,则推出 ( x 0 , y 0 ) (x_0, y_0) (x0,y0)不是互质的,矛盾,必要性成立。
-
因此本题中相当于让我们求解对于 0 ≤ x , y ≤ N 0 \le x, y \le N 0≤x,y≤N,有多少数对 ( x , y ) (x, y) (x,y)是互质的。我们可以先考虑一般的情况,如下图的右下部分:
只考虑右下部分,则和1互质的点为 ϕ ( 1 ) = 1 \phi(1) = 1 ϕ(1)=1个,和2互质的点有 ϕ ( 2 ) = 1 \phi(2) = 1 ϕ(2)=1个,…,和n互质的点有 ϕ ( n ) \phi(n) ϕ(n)个。
- 因为对称性,上半部分的数量等于下半部分的数量,答案为
∑ i = 1 n ϕ ( i ) + 1 \sum _ {i = 1} ^ n \phi(i) + 1 i=1∑nϕ(i)+1
代码
- C++
#include <iostream>
using namespace std;
const int N = 1010;
int primes[N], cnt;
bool st[N];
int phi[N];
// 筛法求欧拉函数
void init(int n) {
phi[1] = 1;
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
phi[i] = i - 1;
}
for (int j = 0; primes[j] * i <= n; j++) {
st[i * primes[j]] = true;
if (i % primes[j] == 0) {
phi[i * primes[j]] = phi[i] * primes[j];
break;
}
phi[i * primes[j]] = phi[i] * (primes[j] - 1);
}
}
}
int main() {
init(N - 1);
int n, m;
cin >> m;
for (int T = 1; T <= m; T++) {
cin >> n;
int res = 1;
for (int i = 1; i <= n; i++) res += phi[i] * 2;
cout << T << ' ' << n << ' ' << res << endl;
}
return 0;
}
AcWing 220. 最大公约数
问题描述
-
问题链接:AcWing 220. 最大公约数
分析
-
如果 ( x , y ) = p (x, y) = p (x,y)=p,且
p
是质数,则 ( x p , y p ) = 1 (\frac{x}{p}, \frac{y}{p})=1 (px,py)=1,说明 x p , y p \frac{x}{p}, \frac{y}{p} px,py互质,令 a = x p , b = y p a =\frac{x}{p}, b = \frac{y}{p} a=px,b=py,则相当于问在 1 ≤ a , b ≤ n p 1 \le a, b \le \frac{n}{p} 1≤a,b≤pn的条件下,互质的点对个数。和AcWing 201. 可见的点求解的内容很类似。 -
对于
1~n
中所有的质数,都需要求互质点对的个数,下面考虑对于某一个质数p
,如何求解,如下图:
-
因此如果令 ϕ ( 1 ) = 0 \phi(1)=0 ϕ(1)=0,则对于对于当前考察的质数
p
,符合要求的点对数目为: 2 × ( ϕ ( 1 ) + ϕ ( 2 ) + . . . + ϕ ( n p ) + 1 2 \times (\phi(1) + \phi(2) + ... + \phi(\frac{n}{p}) + 1 2×(ϕ(1)+ϕ(2)+...+ϕ(pn)+1。 -
我们需要对于
1~n
中所有的质数计算上述表达式,最后相加,因此可以采用前缀和思想求解。
代码
- C++
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e7 + 10;
// 1e7的int和bool数组大小都为40MB,LL数组大小80MB,因此使用存储空间为200MB
int primes[N], cnt;
bool st[N];
int phi[N];
LL s[N];
void init(int n) {
// 默认phi[1] == 0
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes[cnt++] = i;
phi[i] = i - 1;
}
for (int j = 0; primes[j] * i <= n; j++) {
st[i * primes[j]] = true;
if (i % primes[j] == 0) {
phi[primes[j] * i] = phi[i] * primes[j];
break;
}
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + phi[i];
}
int main() {
int n;
cin >> n;
init(n);
LL res = 0;
for (int i = 0; i < cnt; i++) {
int p = primes[i];
res += s[n / p] * 2 + 1;
}
cout << res << endl;
return 0;
}