模板

欧拉函数

欧拉函数 φ(N)\varphi(N) 的定义如下:在 1 ~ N 中所有与 N 互质的数字的个数,计算如下:

N=p1α1p2α2pnαnφ(N)=N(11p1)(11p2)(11pn)\begin{aligned} N&=p_1^{\alpha_1}p_2^{\alpha_2}\cdots p_n^{\alpha_n}\\ \varphi(N)&=N(1-\frac{1}{p_1})(1-\frac{1}{p_2})\cdots(1-\frac{1}{p_n}) \end{aligned}

证明需要用到容斥原理:先把所有 pip_i 的倍数排除,这时会把 pipjp_ip_j 的倍数排除两遍,把 pipjpkp_ip_jp_k 的倍数排除三遍;于是加入 pipjp_ip_j 的倍数,这样会把 pipjpkp_ip_jp_k 的倍数加入三遍;然后再排除 pipjpkp_ip_jp_k 的倍数… 如下:

φ(N)=NNp1Np2Np3+Np1p2+Np2p3+Np1p3+Np1p2p3\begin{aligned} \varphi(N)=N&-\frac{N}{p_1}-\frac{N}{p_2}-\frac{N}{p_3}\cdots\\ &+\frac{N}{p_1p_2}+\frac{N}{p_2p_3}+\frac{N}{p_1p_3}+\cdots\\ &-\frac{N}{p_1p_2p_3}-\cdots\\ \end{aligned}

观察到,所有奇数个 p 相乘的都为负,所有偶数个相乘的都为正,而且相乘的个数取决于有多少个p,因此开头给出的定义式展开符合这一点。

可以进一步说明,对于有 mp 相乘的项共有 CnmC_n^m 个,开头给出的式子中,在 n 个括号中选出 m 个含 p 的项相乘共有 CnmC_n^m 中选法,所以等式成立。

欧拉定理

aaxx 互质,则 aφ(x)1(mod x)a^{\varphi(x)}\equiv 1 (\text{mod }x),证明如下(下面所有的 ki,Kik_i, K_i 都代表某个整数):

设所有小于 xx 的数中所有与 xx 互质的数是 x1,x2,,xφ(x)x_1, x_2, \cdots, x_{\varphi(x)},那么 ax1,ax2,,axφ(x)ax_1, ax_2, \cdots, ax_{\varphi(x)} 也与 xx 互质(无论是 aa 还是 xix_i 都与 xx 没有相同的质因数,所以 axiax_i 也与 xx 没有相同的质因数)。

并且 ax1,ax2,,axφ(x)ax_1, ax_2, \cdots, ax_{\varphi(x)}xx 取模两两不相同,这步用反证法证明,假设 axi,axjax_i, ax_j 同余:

axiaxj=(k1k2)x=a(xixj)\begin{aligned} ax_i-ax_j&=(k_1-k_2)x\\ &=a(x_i-x_j)\\ \end{aligned}

由于 aaxx 互质,那么如果这个等式成立只能有 xixj=K1xx_i-x_j=K_1x,又因为 xixj<xx_i-x_j<x,所以矛盾。

于是这两组数模 xx 之后其实是同一组数据,那么它们取模之后全部乘起来结果是相同的。这里两边再取一遍模,使用模运算的乘法分配律。

i=1ϕ(x)[(axi) mod x]=i=1ϕ(x)xiaϕ(x)i=1ϕ(x)xii=1ϕ(x)xi(mod x)\begin{aligned} \prod_{i=1}^{\phi(x)}[(ax_i)\text{ mod }x]&=\prod_{i=1}^{\phi(x)}x_i\\ a^{\phi(x)}\prod_{i=1}^{\phi(x)}x_i&\equiv \prod_{i=1}^{\phi(x)}x_i (\text{mod }x)\\ \end{aligned}

由于任意一个 xix_i 都与 xx 互质,所以它们的乘积也与 xx 互质,设它们的乘积为 bK2xb \ne K_2x,有:

aφ(x)b=k1x+r,b=k2x+raφ(x)bb=(aφ(x)1)b=(k1k2)xaφ(x)1=K3xaφ(x)1(mod x)\begin{aligned} a^{\varphi(x)}b&=k_1x+r,b=k_2x+r\\ a^{\varphi(x)}b-b&=(a^{\varphi(x)}-1)b\\ &=(k_1-k_2)x\\ &\Rightarrow a^{\varphi(x)}-1=K_3x\\ &\Rightarrow a^{\varphi(x)}\equiv 1 (\text{mod }x) \end{aligned}

特别地,如果设 pp 是质数,那么 aφ(p)ap11(mod p)a^{\varphi(p)}\equiv a^{p-1} \equiv 1(\text{mod }p),欧拉定理是费马小定理的拓展。

定义法求欧拉函数

先找质因数,然后累项求积。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int euler(int a) {
int res = a;
for (int i = 2; i <= a/i; i++) {
// 这里就是先用 if 然后在里面把 a 除干净
if (a % i == 0) {
// res(1-1/i) = res-res/i
res = res - res/i;
while (a % i == 0) a /= i;
}
}
// 一定注意不要落了 a 没除干净的情况
if (a > 1) res = res-res/a;
return res;
}

线性筛选法求欧拉函数

与筛选质数时的模板类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <bitset>
#include <iostream>
using namespace std;

const int N = 1e6+10;
int phi[N], primes[N], cnt=0;
bitset<N> st;

void eular(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[i * primes[j]] = 1;
if (i % primes[j] == 0) {
phi[i * primes[j]] = phi[i] * primes[j];
break;
} else {
phi[i * primes[j]] = phi[i] * (primes[j] - 1);
}
}
}
}

可以证明当要求 ipji\cdot p_j 的时候,φ(i)\varphi(i) 一定已经求出来了:

  1. 如果 ii 是质数,那么它会在进入内层 for 循环前被求出来。
  2. 如果 ii 是合数,那么它会在 ii 循环到自己之前就被筛掉,筛掉的同时也就求出来了 φ(i)\varphi(i)

接下来通过公式证明 φ(i)\varphi(i) 的这几个递推式:

  1. 如果 ii 是质数,那么在 1i11 \sim i-1 中所有数都与它互质,所以有 φ(i)=i1\varphi(i)=i-1
  2. 如果 i mod pj=0i \text{ mod } p_j=0,此时 pjp_jii 的最小质因数,ii 表示为 p1α1p2α2p_1^{\alpha_1}p_2^{\alpha_2}\cdots 这个式子中一定包含了 pjp_j,由欧拉函数的定义知,φ(pji)=pjφ(i)\varphi(p_j\cdot i)=p_j\varphi(i)
  3. 如果 i mod pj0i\text{ mod }p_j\ne 0,此时 pjp_j 不是 ii 的质因数,那么 φ(pji)=φ(pj)φ(i)=(pj1)φ(i)\varphi(p_j\cdot i)=\varphi(p_j)\cdot \varphi(i)=(p_j-1)\varphi(i)

例题

可见的点

在一个平面直角坐标系的第一象限内,如果一个点 (x,y) 与原点 (0,0) 的连线中没有通过其他任何点,则称该点在原点处是可见的。

例如,点 (4,2) 就是不可见的,因为它与原点的连线会通过点 (2,1)。

编写一个程序,计算给定整数 N 的情况下,满足 0≤x, y≤N 的可见点 (x,y) 的数量(可见点不包括原点)。

题目链接:AcWing 201

如果一个点 (x0,y0)(x_0,y_0) 可见,必有 k=y0/x0k=y_0/x_0 不可化简,也就是 y0,x0y_0, x_0 互质。

所以答案是 2i=1nφ(i)+12\sum_{i=1}^n \varphi(i)+1,去掉一个直线 y=xy=x,加上两个直线 y=0,x=0y=0,x=0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <vector>
using namespace std;

const int N = 1010, m = N-1;
int phi[N];
bool st[N];
vector<int> primes;

void init() {
phi[1] = 1;
for (int i = 2; i <= m; i++) {
if (!st[i]) {
primes.push_back(i);
phi[i] = i-1;
}
for (int j = 0; primes[j] <= m/i; 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();
int T; scanf("%d", &T);
for (int i = 1; i <= T; i++) {
int n, sum = 0;
scanf("%d", &n);
for (int j = 1; j <= n; j++)
sum += phi[j];
printf("%d %d %d\n", i, n, 2*sum+1);
}
return 0;
}

最大公约数

给定整数 N,求 1≤x,y≤N 且 GCD(x,y) 为素数的数对 (x,y) 有多少对。

题目链接:AcWing 220

题目是求(中间当作一个布尔表达式):

pi=1nj=1n[gcd(i,j)=p]\sum_p \sum _{i=1}^n\sum_{j=1}^n [\gcd(i,j)=p]

i,ji,jpp 的倍数,那么下列式子等价:

gcd(i,j)=pgcd(i/p,j/p)=1\gcd(i,j)=p \Leftrightarrow \gcd(i/p,j/p)=1

因此枚举 i,j=kpi,j=kp 中的 kk 就可以:

pi=1n/pj=1n/p[gcd(i,j)=1]\sum_p \sum_{i=1}^{\lfloor n/p \rfloor}\sum_{j=1}^{\lfloor n/p \rfloor}[\gcd(i,j)=1]

等价于:

p[2i=1n/pφ(i)1]\sum_p[2\sum_{i=1}^{\lfloor n/p \rfloor} \varphi(i)-1]

对欧拉函数简单求一个前缀和就可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <vector>
using namespace std;

typedef long long LL;
const int N = 1e7+10;
int phi[N];
LL s[N];
bool st[N];
vector<int> primes;

void init(int n) {
s[1] = phi[1] = 1;
for (int i = 2; i <= n; i++) {
if (!st[i]) {
primes.push_back(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);
}

s[i] = s[i-1] + phi[i];
}
}

int main() {
int n;
cin >> n;
init(n);
LL ans = 0;
for (int p : primes) {
ans += 2*s[n/p]-1;
}
cout << ans << endl;
return 0;
}

最大公约数求和

给定一个整数 NN,求出 i=1ngcd(i,n)\sum_{i=1}^n\gcd(i,n)

题目链接:P2303AcWing 221

首先一定有 gcd(i,n)n\gcd(i,n)|n,因此枚举 nn 的所有约数,看它在整个过程中出现了几次。

对于其中一个约数 dd,当 gcd(i,n)=d\gcd(i,n)=d 时,有 gcd(i/d,n/d)=1\gcd(i/d,n/d)=1,其中 i/d[1,n/d]i/d\in[1,n/d],因此它出现的个数就是 φ(n/d)\varphi(n/d),这样就有:

i=1ngcd(i,n)=dndφ(nd)=dnndφ(d)=ndni=1kd(11pi)\begin{aligned} \sum_{i=1}^n\gcd(i,n)&=\sum_{d|n}d\varphi(\frac{n}{d})\\ &=\sum_{d|n}\frac{n}{d}\varphi(d)\\ &=n\sum_{d|n}\prod_{i=1}^{k_d}(1-\frac{1}{p_i}) \end{aligned}

这里有一个常用的技巧,就是如果是枚举所有约数的某个性质的和,可以借鉴约数之和公式。

(1+p11+p12++p1c1)(1+p21+p22++p2c2)(1+p_1^1+p_1^2+\cdots+p_1^{c_1})(1+p_2^1+p_2^2+\cdots+p_2^{c_2})\cdots

映射到下面:

[1+(11p1)+(11p1)++(11p1)]=i=1k[1+ci(11pi)][1+(1-\frac{1}{p_1})+(1-\frac{1}{p_1})+\cdots+(1-\frac{1}{p_1})]\cdots\\ =\prod_{i=1}^k [1+c_i(1-\frac{1}{p_i})]

所以原式等于:

ni=1k[1+ci(11pi)]n\prod_{i=1}^k[1+c_i(1-\frac{1}{p_i})]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <cstdio>
using namespace std;

typedef long long LL;

int main() {
LL n;
scanf("%lld", &n);
LL res = n;

for (int i = 2; i <= n/i; i++) {
if (n % i == 0) {
int c = 0, p = i;
while (n % i == 0) n /= i, c++;
// 先除再乘防溢出
res /= p;
res = res * (p + c*(p-1));
}
}
if (n > 1) {
res /= n;
res = res * (n + n - 1);
}
printf("%lld\n", res);
return 0;
}