模板
欧拉函数
欧拉函数 φ ( N ) \varphi(N) φ ( N ) 的定义如下:在 1 ~ N 中所有与 N 互质的数字的个数,计算如下:
N = p 1 α 1 p 2 α 2 ⋯ p n α n φ ( N ) = N ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) ⋯ ( 1 − 1 p n ) \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}
N φ ( N ) = p 1 α 1 p 2 α 2 ⋯ p n α n = N ( 1 − p 1 1 ) ( 1 − p 2 1 ) ⋯ ( 1 − p n 1 )
证明需要用到容斥原理:先把所有 p i p_i p i 的倍数排除,这时会把 p i p j p_ip_j p i p j 的倍数排除两遍,把 p i p j p k p_ip_jp_k p i p j p k 的倍数排除三遍;于是加入 p i p j p_ip_j p i p j 的倍数,这样会把 p i p j p k p_ip_jp_k p i p j p k 的倍数加入三遍;然后再排除 p i p j p k p_ip_jp_k p i p j p k 的倍数… 如下:
φ ( N ) = N − N p 1 − N p 2 − N p 3 ⋯ + N p 1 p 2 + N p 2 p 3 + N p 1 p 3 + ⋯ − N p 1 p 2 p 3 − ⋯ \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}
φ ( N ) = N − p 1 N − p 2 N − p 3 N ⋯ + p 1 p 2 N + p 2 p 3 N + p 1 p 3 N + ⋯ − p 1 p 2 p 3 N − ⋯
观察到,所有奇数个 p
相乘的都为负,所有偶数个相乘的都为正,而且相乘的个数取决于有多少个p
,因此开头给出的定义式展开符合这一点。
可以进一步说明,对于有 m
个 p
相乘的项共有 C n m C_n^m C n m 个,开头给出的式子中,在 n
个括号中选出 m
个含 p
的项相乘共有 C n m C_n^m C n m 中选法,所以等式成立。
欧拉定理
若 a a a 与 x x x 互质,则 a φ ( x ) ≡ 1 ( mod x ) a^{\varphi(x)}\equiv 1 (\text{mod }x) a φ ( x ) ≡ 1 ( mod x ) ,证明如下(下面所有的 k i , K i k_i, K_i k i , K i 都代表某个整数):
设所有小于 x x x 的数中所有与 x x x 互质的数是 x 1 , x 2 , ⋯ , x φ ( x ) x_1, x_2, \cdots, x_{\varphi(x)} x 1 , x 2 , ⋯ , x φ ( x ) ,那么 a x 1 , a x 2 , ⋯ , a x φ ( x ) ax_1, ax_2, \cdots, ax_{\varphi(x)} a x 1 , a x 2 , ⋯ , a x φ ( x ) 也与 x x x 互质(无论是 a a a 还是 x i x_i x i 都与 x x x 没有相同的质因数,所以 a x i ax_i a x i 也与 x x x 没有相同的质因数)。
并且 a x 1 , a x 2 , ⋯ , a x φ ( x ) ax_1, ax_2, \cdots, ax_{\varphi(x)} a x 1 , a x 2 , ⋯ , a x φ ( x ) 对 x x x 取模两两不相同,这步用反证法证明,假设 a x i , a x j ax_i, ax_j a x i , a x j 同余:
a x i − a x j = ( k 1 − k 2 ) x = a ( x i − x j ) \begin{aligned}
ax_i-ax_j&=(k_1-k_2)x\\
&=a(x_i-x_j)\\
\end{aligned}
a x i − a x j = ( k 1 − k 2 ) x = a ( x i − x j )
由于 a a a 与 x x x 互质,那么如果这个等式成立只能有 x i − x j = K 1 x x_i-x_j=K_1x x i − x j = K 1 x ,又因为 x i − x j < x x_i-x_j<x x i − x j < x ,所以矛盾。
于是这两组数模 x x x 之后其实是同一组数据,那么它们取模之后全部乘起来结果是相同的。这里两边再取一遍模,使用模运算的乘法分配律。
∏ i = 1 ϕ ( x ) [ ( a x i ) mod x ] = ∏ i = 1 ϕ ( x ) x i a ϕ ( x ) ∏ i = 1 ϕ ( x ) x i ≡ ∏ i = 1 ϕ ( x ) x i ( 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}
i = 1 ∏ ϕ ( x ) [( a x i ) mod x ] a ϕ ( x ) i = 1 ∏ ϕ ( x ) x i = i = 1 ∏ ϕ ( x ) x i ≡ i = 1 ∏ ϕ ( x ) x i ( mod x )
由于任意一个 x i x_i x i 都与 x x x 互质,所以它们的乘积也与 x x x 互质,设它们的乘积为 b ≠ K 2 x b \ne K_2x b = K 2 x ,有:
a φ ( x ) b = k 1 x + r , b = k 2 x + r a φ ( x ) b − b = ( a φ ( x ) − 1 ) b = ( k 1 − k 2 ) x ⇒ a φ ( x ) − 1 = K 3 x ⇒ a φ ( 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}
a φ ( x ) b a φ ( x ) b − b = k 1 x + r , b = k 2 x + r = ( a φ ( x ) − 1 ) b = ( k 1 − k 2 ) x ⇒ a φ ( x ) − 1 = K 3 x ⇒ a φ ( x ) ≡ 1 ( mod x )
特别地,如果设 p p p 是质数,那么 a φ ( p ) ≡ a p − 1 ≡ 1 ( mod p ) a^{\varphi(p)}\equiv a^{p-1} \equiv 1(\text{mod }p) a φ ( p ) ≡ a p − 1 ≡ 1 ( 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 % i == 0 ) { res = res - res/i; while (a % i == 0 ) a /= i; } } 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 ); } } } }
可以证明当要求 i ⋅ p j i\cdot p_j i ⋅ p j 的时候,φ ( i ) \varphi(i) φ ( i ) 一定已经求出来了:
如果 i i i 是质数,那么它会在进入内层 for
循环前被求出来。
如果 i i i 是合数,那么它会在 i i i 循环到自己之前就被筛掉,筛掉的同时也就求出来了 φ ( i ) \varphi(i) φ ( i ) 。
接下来通过公式证明 φ ( i ) \varphi(i) φ ( i ) 的这几个递推式:
如果 i i i 是质数,那么在 1 ∼ i − 1 1 \sim i-1 1 ∼ i − 1 中所有数都与它互质,所以有 φ ( i ) = i − 1 \varphi(i)=i-1 φ ( i ) = i − 1 。
如果 i mod p j = 0 i \text{ mod } p_j=0 i mod p j = 0 ,此时 p j p_j p j 是 i i i 的最小质因数,i i i 表示为 p 1 α 1 p 2 α 2 ⋯ p_1^{\alpha_1}p_2^{\alpha_2}\cdots p 1 α 1 p 2 α 2 ⋯ 这个式子中一定包含了 p j p_j p j ,由欧拉函数的定义知,φ ( p j ⋅ i ) = p j φ ( i ) \varphi(p_j\cdot i)=p_j\varphi(i) φ ( p j ⋅ i ) = p j φ ( i ) 。
如果 i mod p j ≠ 0 i\text{ mod }p_j\ne 0 i mod p j = 0 ,此时 p j p_j p j 不是 i i i 的质因数,那么 φ ( p j ⋅ i ) = φ ( p j ) ⋅ φ ( i ) = ( p j − 1 ) φ ( i ) \varphi(p_j\cdot i)=\varphi(p_j)\cdot \varphi(i)=(p_j-1)\varphi(i) φ ( p j ⋅ i ) = φ ( p j ) ⋅ φ ( i ) = ( p j − 1 ) φ ( i ) 。
例题
可见的点
在一个平面直角坐标系的第一象限内,如果一个点 (x,y) 与原点 (0,0) 的连线中没有通过其他任何点,则称该点在原点处是可见的。
例如,点 (4,2) 就是不可见的,因为它与原点的连线会通过点 (2,1)。
编写一个程序,计算给定整数 N 的情况下,满足 0≤x, y≤N 的可见点 (x,y) 的数量(可见点不包括原点)。
题目链接:AcWing 201 。
如果一个点 ( x 0 , y 0 ) (x_0,y_0) ( x 0 , y 0 ) 可见,必有 k = y 0 / x 0 k=y_0/x_0 k = y 0 / x 0 不可化简,也就是 y 0 , x 0 y_0, x_0 y 0 , x 0 互质。
所以答案是 2 ∑ i = 1 n φ ( i ) + 1 2\sum_{i=1}^n \varphi(i)+1 2 ∑ i = 1 n φ ( i ) + 1 ,去掉一个直线 y = x y=x y = x ,加上两个直线 y = 0 , x = 0 y=0,x=0 y = 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 。
题目是求(中间当作一个布尔表达式):
∑ p ∑ i = 1 n ∑ j = 1 n [ gcd ( i , j ) = p ] \sum_p \sum _{i=1}^n\sum_{j=1}^n [\gcd(i,j)=p]
p ∑ i = 1 ∑ n j = 1 ∑ n [ g cd( i , j ) = p ]
若 i , j i,j i , j 为 p p p 的倍数,那么下列式子等价:
gcd ( i , j ) = p ⇔ gcd ( i / p , j / p ) = 1 \gcd(i,j)=p \Leftrightarrow \gcd(i/p,j/p)=1
g cd( i , j ) = p ⇔ g cd( i / p , j / p ) = 1
因此枚举 i , j = k p i,j=kp i , j = k p 中的 k k k 就可以:
∑ p ∑ i = 1 ⌊ n / p ⌋ ∑ j = 1 ⌊ n / 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 ∑ i = 1 ∑ ⌊ n / p ⌋ j = 1 ∑ ⌊ n / p ⌋ [ g cd( i , j ) = 1 ]
等价于:
∑ p [ 2 ∑ i = 1 ⌊ n / p ⌋ φ ( i ) − 1 ] \sum_p[2\sum_{i=1}^{\lfloor n/p \rfloor} \varphi(i)-1]
p ∑ [ 2 i = 1 ∑ ⌊ n / p ⌋ φ ( 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 ; }
最大公约数求和
给定一个整数 N N N ,求出 ∑ i = 1 n gcd ( i , n ) \sum_{i=1}^n\gcd(i,n) ∑ i = 1 n g cd( i , n )
题目链接:P2303 ,AcWing 221 。
首先一定有 gcd ( i , n ) ∣ n \gcd(i,n)|n g cd( i , n ) ∣ n ,因此枚举 n n n 的所有约数,看它在整个过程中出现了几次。
对于其中一个约数 d d d ,当 gcd ( i , n ) = d \gcd(i,n)=d g cd( i , n ) = d 时,有 gcd ( i / d , n / d ) = 1 \gcd(i/d,n/d)=1 g cd( i / d , n / d ) = 1 ,其中 i / d ∈ [ 1 , n / d ] i/d\in[1,n/d] i / d ∈ [ 1 , n / d ] ,因此它出现的个数就是 φ ( n / d ) \varphi(n/d) φ ( n / d ) ,这样就有:
∑ i = 1 n gcd ( i , n ) = ∑ d ∣ n d φ ( n d ) = ∑ d ∣ n n d φ ( d ) = n ∑ d ∣ n ∏ i = 1 k d ( 1 − 1 p i ) \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}
i = 1 ∑ n g cd( i , n ) = d ∣ n ∑ d φ ( d n ) = d ∣ n ∑ d n φ ( d ) = n d ∣ n ∑ i = 1 ∏ k d ( 1 − p i 1 )
这里有一个常用的技巧,就是如果是枚举所有约数的某个性质的和,可以借鉴约数之和公式。
( 1 + p 1 1 + p 1 2 + ⋯ + p 1 c 1 ) ( 1 + p 2 1 + p 2 2 + ⋯ + p 2 c 2 ) ⋯ (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 + p 1 1 + p 1 2 + ⋯ + p 1 c 1 ) ( 1 + p 2 1 + p 2 2 + ⋯ + p 2 c 2 ) ⋯
映射到下面:
[ 1 + ( 1 − 1 p 1 ) + ( 1 − 1 p 1 ) + ⋯ + ( 1 − 1 p 1 ) ] ⋯ = ∏ i = 1 k [ 1 + c i ( 1 − 1 p i ) ] [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})]
[ 1 + ( 1 − p 1 1 ) + ( 1 − p 1 1 ) + ⋯ + ( 1 − p 1 1 )] ⋯ = i = 1 ∏ k [ 1 + c i ( 1 − p i 1 )]
所以原式等于:
n ∏ i = 1 k [ 1 + c i ( 1 − 1 p i ) ] n\prod_{i=1}^k[1+c_i(1-\frac{1}{p_i})]
n i = 1 ∏ k [ 1 + c i ( 1 − p 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 #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 ; }