数论学习笔记之欧拉函数
欧拉函数
欧拉函数是什么
定义 欧拉函数 \(\varphi(n)\) 表示从 \(1\) 到 \(n\) 中与 \(n\) 互质的数的个数
若在算数基本定理中 \(N=p_1 ^ {c_1}p_2 ^ {c_2}...p_m ^ {c_m}\) , 则有:
等同于
证明
设 \(p\) 为 \(N\) 的质因子, 那么1到N中p的倍数有 \(p, 2p, 3p, ..., (N/p) * p\), 一共有 \(N/p\) 个
同理, 我们设 \(q\) 也为 \(N\) 的另一个质因子, 那么 \(1\) 到 \(N\) 中为 \(q\) 的倍数有 \(N/q\) 个
那么如果我们将 \(N/p + N/q\) 个数字去掉就会发现同时为 \(p\) 和 \(q\) 的倍数的数字被去除了两次, 要加回来一次
然后我们就会得出如下式子:
其实就是运用了容斥原理
同样的, 我们可以用这个方式去除其他质因子的倍数, 得到的就是 \(1\) 到 \(N\) 中与 \(N\) 互质的数的个数, 最终推得上面的式子
证毕.
性质
积性函数
定义: 若\(a, b\)互质, 有 \(f(a)*f(b) = f(ab)\) , 那么就称 \(f\) 为积性函数
欧拉函数就是一个积性函数
那么欧拉函数就拥有了积性函数的性质, 即
-
\(\varphi(ab) = \varphi(a)*\varphi(b)\)
-
当有 $ n = \prod_{i = 1} ^ {m} p_i ^ {c _ i}$, 则 \(\varphi(n) = \prod_ {i = 1} ^ {m} \varphi(p_i ^ {c_ i})\)
证明(1)
《算法竞赛进阶指南》上只有一句话:
根据欧拉函数的计算式, 对 \(a, b\) 进行分解质因数, 直接可得性质2. 把性质2推广到一般的函数上, 可以得到 "积性函数" 的概念.
证毕.
额, 怎么不算证明呢
本人时间有限实际就是懒得写, 读者可以自己根据这个证明一下, 这里不再进行其他说明
证明(2)
对\(n\)分解质因数, 再结合积性函数的定义, 显然性质2成立.
证毕.
其他说明我觉得不太必要了, 有需要可以自己算算
欧拉函数的性质
-
上面有了
-
同上
-
$ \forall n > 1$ , \(1\) 到 \(n\) 中与 \(n\) 互质的数字和为 \(n * \varphi(n)/2\)
-
若 \(n\) 为质数, 有\(\varphi(n) = n - 1\)
-
若 \(n\) 为质数, 有 \(\varphi(n ^ k) = (n - 1) * n ^{k - 1} = n ^ k - n ^{k - 1}\)
-
若 \(p\) 为质数, 若 \(p\mid n\) 但是 \(p ^ 2 \mid n\) , 则 \(\varphi(n) = \varphi(n/p) * p\)
-
若 \(p\) 为质数, 若 \(p\mid n\) 但是 \(p ^ 2 \nmid n\) , 则 \(\varphi(n) = \varphi(n/p) * (p - 1)\)
-
\(\sum _ {d \mid n} \varphi(d) = n\)
证明(3 ~ 6)
-
略
-
略
-
因为 \(gcd(n, x) = gcd(n, n - x)\) , 所以我们发现与
\(n\) 不互质的两个数 \(x, n - x\) 总是成对出现, 平均值 \(n / 2\) , 得证. -
显然
-
其实就是定义式的变形, 可以自己带个数算算, 也挺显然的
-
若 \(p\) 为质数, \(p\mid n\) 但是 \(p ^ 2 \mid n\) , 则有\(n, n/p\) 包含相同的质因子, 因为积性函数, 只是 \(p\) 的指数不同. 直接写 \(\varphi (n)\) 与 \(\varphi (n/p)\) 的公式, 两者相除得 \(p\) , 得证.
-
若 \(p\) 为质数, 若 \(p\mid n\) 但是 \(p ^ 2 \nmid n\) , 说明 \(p, n/p\) 互质, 由积性函数得 \(\varphi(n) = \varphi(n/p) * \varphi(p)\) , 又有 \(\varphi(p) = p - 1\) , 得证.
-
设 \(f(n) = \sum _{d \mid n} \varphi(d)\) .
由乘法分配律和积性函数, 易得 \(f(nm) = \sum _ {d \mid nm} \varphi(d) = (\,\sum_ {d \mid n} \varphi(d)\,) * (\,\sum _{d \mid m} \varphi(d)\,) = f(n) * f(m)\).
现在我们知道了我们设的函数为一个积性函数.
那么对于单个质因子有 \(f(p ^ m) = \sum _ {d \mid p ^ m} = \varphi(1) + \varphi(p) + \varphi(p ^ 2) + ... + \varphi(p ^ {m - 1})\), 相当于一个等比函数再加一, 最终为 \(p ^ m\) .
那么就有 \(f(n) = \prod _ {i = 1} ^ {m} f(p_i ^{c_i}) = \prod _ {i = 1} ^ {m} p_i ^ {c _ i} = n\) .
证毕.
求欧拉函数
累死了终于不用到处证明了
试除法
都试除法了, 那肯定是使用的唯一分解定理啦
我们直接把之前推过的式子搬下来
显然, 上面的式子等同于:
\(p \mid N\) 表示的就是N的质因子
欧拉函数仅由 \(n\) 与质因子决定, 与次数无关
分解质因数的复杂度为 \(O(\sqrt n)\) , 计算欧拉函数的复杂度为 \(O(\sqrt n)\) , 若要求 \(2\) ~ \(n\) 的欧拉函数的话就是 \(O(n \sqrt n)\)
int phi(int n) { //试除法求欧拉函数(n)
int res = n;
for (int i = 2; i * i <= n; i++){
if (n % i == 0) res = res / i * (i - 1);
while (n % i == 0) n /= i;
}
if (n > 1) res = res / n * (n - 1);
return res;
}
埃氏筛
同样使用欧拉函数的计算公式
求出 \(2\) ~ \(n\) 的欧拉函数的时间复杂度为 \(O(n \sqrt n)\)
void varphii(int n) { //埃氏筛求欧拉函数(2 ~ n)
for (int i = 2; i <= n; i++) phi[i] = i;
for (int i = 2; i <= n; i++)
if (phi[i] == i)
for (int j = i; j <= n; j += i)
phi[j] = phi[j] / i * (i - 1);
}
线性筛
用以上两种算法已经能解决大部分求欧拉函数的问题
但是有没有一种算法能优化时间复杂度呢?
有的兄弟, 有的
用线性筛的思想就能将求出 \(2\) ~ \(n\) 的欧拉函数的时间复杂度降到 \(O(n)\)
在线性筛中, 每个数都只会被自己的最小的质因子筛掉
结合我们之前列出的性质6与性质7
- 若 \(p\) 为质数, 若 \(p\mid n\) 但是 \(p ^ 2 \mid n\) , 则 \(\varphi(n) = \varphi(n/p) * p\)
- 若 \(p\) 为质数, 若 \(p\mid n\) 但是 \(p ^ 2 \nmid n\) , 则 \(\varphi(n) = \varphi(n/p) * (p - 1)\)
我们就可以用 \(\varphi(n / p)\) 推出 \(\varphi(n)\)
然后就是你们最喜欢的代码环节
int phi[MN], vis[MN], p[MN], cnt;
//欧拉函数; 访问标记(未标记的为质数); 质数; 质数计数器
void varphi(int n){ //线性筛求欧拉函数(1 ~ n)
phi[1] = 1;
for (int i = 2; i <= n; i++){
if (vis[i] == 0){
p[++cnt] = i; //下标从1开始!!
phi[i] = i - 1; //若为质数则减一
}
for (int j = 1; j <= cnt; j++){
if (i * p[j] > n) break;
int m = i * p[j];
vis[m] = 1; //非质数标记
if (i % p[j] == 0){ //i能被p[j]整除则i包含了m的所有质因子
phi[m] = phi[i] * p[j];
break;
}
else { //不能整除说明互质
phi[m] = phi[i] * (p[j] - 1); //注: phi[j] = p[j] - 1
}
}
}
}
例题
题目描述
作为体育委员,C 君负责这次运动会仪仗队的训练。仪仗队是由学生组成的 \(N \times N\) 的方阵,为了保证队伍在行进中整齐划一,C 君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图)。
现在,C 君希望你告诉他队伍整齐时能看到的学生人数。
输入格式
一行,一个正整数 \(N\)。
输出格式
输出一行一个数,即 C 君应看到的学生人数。
输入输出样例 #1
输入 #1
4
输出 #1
9
说明/提示
对于 \(100 \%\) 的数据,\(1 \le N \le 40000\)。
解题思路
我们仔细观察上图, 注意到若一个学生能被看到, 那么他所站的位置距离C君的水平距离 \((x)\) 和竖直距离 \((y)\) 满足 \(gcd(x, y) = 1\)
那么 当 \(n > 2\) 时 \(n\) 每增大 \(1\) , 在同一行或同一列上新的能看到的学生数量就是与 \(n - 1\) 互质的学生数量, 即 \(\varphi (n - 1)\) , 可以自己列一下感受一下
我们还发现像这样的学生总是成对出现, 结合打表 (我认真的) , 很容易就能得出这样的式子 (我这里设了个函数 \(f\) 表示答案) :
然后...上代码吧
代码
#include <bits/stdc++.h>
using namespace std;
const int MN = 40003;
int n, ans = 1;
int phi[MN], vis[MN], p[MN], cnt;
int main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
if (n == 1) {
cout << 0 << endl;
return 0;
}
phi[1] = 1;
for (int i = 2; i < n; i++){
if (vis[i] == 0){
p[++cnt] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= cnt; j++){
if (i * p[j] > n) break;
int m = i * p[j];
vis[m] = 1;
if (i % p[j] == 0){
phi[m] = phi[i] * p[j];
break;
}
else {
phi[m] = phi[i] * (p[j] - 1);
}
}
}
for (int i = 1; i < n; i++) ans += phi[i] * 2;
cout << ans;
return 0;
}
内容参考《算法竞赛进阶指南》与上课的课件