欧拉函数
欧拉函数
[AcWing873] 欧拉函数
题目描述
给定 \(n\) 个正整数 \(a_i\),请你求出每个数的欧拉函数。
欧拉函数的定义
\(1 \sim N\) 中与 \(N\) 互质的数的个数被称为欧拉函数,记为 \(ϕ(N)\)。
若在算数基本定理中,\(N = p_1^{a_1}p_2^{a_2}…p_m^{a_m}\),则:
\(ϕ(N)\) = \(N \times \frac{p_1-1}{p_1} \times \frac{p_2-1}{p_2} \times … \times \frac{p_m-1}{p_m}\)
输入格式
第一行包含整数 \(n\)。
接下来 \(n\) 行,每行包含一个正整数 \(a_i\)。
输出格式
输出共 \(n\) 行,每行输出一个正整数 \(a_i\) 的欧拉函数。
数据范围
\(1 \le n \le 100\),
\(1 \le a_i \le 2 \times 10^9\)
输入样例:
3
3
6
8
输出样例:
2
2
4
算法
这里给出欧拉函数的部分知识。
欧拉函数的定义
$1\sim n $ 中与 \(n\) 互质的数的个数成为欧拉函数,记为 \(ϕ(n)\) 。
例:\(\phi(1)=1,\phi(2)=1,\phi(3)=2,\phi(4)=2,\phi(5)=4\) 。
欧拉函数的性质
- 若 \(p\) 是质数,则 \(ϕ(p)=p-1\) 。
- 若 \(p\) 是质数,则 \(\phi(p^k)=(p-1)p^{k-1}\) 。
- 积性函数:若 \(gcd(m,n)=1\),则 \(\phi(mn)=\phi(m)\phi(n)\) 。
欧拉函数是一个积性函数。
欧拉函数的计算公式
由唯一分解定理,对于一个正整数 \(n(n>1)\) 可以分解质因数,得到
则
欧拉函数只与 \(n\) 及其质因子 \(p_i\) 决定,与质因子次数 \(a_i\) 无关。
对于 \(p\) 为质数的情况,我们有 \(\phi(p)=p\times (1-\frac{1}{p})=p-1\) ,性质1得证。
以下代码就是基于 \(\phi(n)\) 的表达式编写。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
int main()
{
int T; cin >> T;
while (T--){
int n; cin >> n;
// 注意这里要先乘一个n
auto ans = n * 1ll;
unordered_map<int,int> hash;
for (int i = 2; i <= n / i; i++){
while (n % i == 0){
n /= i;
hash[i] ++;// 次数更新
}
// 把质因子i给除尽了
}
if (n > 1) hash[n] ++;
for (auto i : hash){
int p = i.first;
ans = ans * (p - 1) / p;// 每次乘(p-1)/p
}
cout << ans << endl;
}
return 0;
}
[AcWing874] 筛法求欧拉函数
题目描述
给定一个正整数 \(n\),求 \(1 \sim n\) 中每个数的欧拉函数之和。
输入格式
共一行,包含一个整数 \(n\)。
输出格式
共一行,包含一个整数,表示 \(1 \sim n\) 中每个数的欧拉函数之和。
数据范围
\(1 \le n \le 10^6\)
输入样例:
6
输出样例:
12
算法
这里利用了线性筛(欧拉筛)的方法。
具体方法可见质数的线性筛知识。
此处的筛是指利用数组 \(vis\) 判断有无遍历
第一层循环:\(i=2\sim n\) ,判断 \(i\) 是否为 \(n\) 的因子。
如果 \(i\) 没有遍历过,说明一定是一个最小质因子,\(\phi(i)=i-1\) (性质1),并加入质数数组 \(prime\) 。
第二层循环:\(j=0\sim (i\times prime[j]\leq n)\) ,遍历质数数组 \(prime\) 。
对于合数 \(m=i\times prime[j]\) ,先用 \(vis\) 筛掉 \(m\) ,并讨论两种情况:
-
如果 \(i\ \%\ prime[j]==0\),即 \(i\) 和 \(prime[j]\) 不互质,即 \(gcd(i,prime[j])\ne 1\) ,不可以用性质3, \(i\) 本身就是个合数。
那么 \(i\) 就包含了 \(m\) 的所有质因子,即 \(i\) 和 \(m\) 的质因子一样。
所以 \(\phi(m)=prime[j]\times \phi(i)\) 。
此处加以证明:
设 \(m=i\times p\) ,其中 \(p\) 为质数。
若 \(p\) 为 \(i\) 的因子,则对于 \(i=\prod_{j=1}^{k}p_{j}^{a_j}\) ,必定存在 \(p_t\) 使 \(p_t=p\) ,指数为 \(a_t\) 。
其中 \(\phi(i)=i\times \prod_{j=1}^{k}(1-\frac{1}{p_j})\) 。
那么对于 \(m=\prod_{s=1}^{k}p_{s}^{a_s}=p_{1}^{a_1}p_{2}^{a_2}\cdots p_{t}^{a_t+1}\cdots p_{k}^{a_k}\) ,可得到
\(\phi(m)=m\times \prod_{s=1}^{k}(1-\frac{1}{p_s})=p\times i\times \prod_{s=1}^{k}(1-\frac{1}{p_s})\) 。
可以发现右边的求积和 \(\phi(i)\) 形式一模一样,直接带入,就得到 \(\phi(m)=p\times \phi(i)\) 。
-
如果 \(i\ \%\ prime[j]!=0\) ,即 \(i\) 和 \(prime[j]\) 互质,即\(gcd(i,prime[j])= 1\),那么利用性质1、3,得
\(\phi(m)=\phi(prime[j])\times \phi(i)=(prime[j]-1)\times \phi(i)\) 。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int vis[N];// 记录是否筛掉
int prime[N], cnt; // 记录质因子,cnt是prime数组的个数
int phi[N];// 记录欧拉函数
long long sum[N];// 记录1~i的欧拉函数的和,记得开long long
int main()
{
int n; cin >> n;
// 一般的,我们定义1的欧拉函数是1
phi[1] = 1;
sum[1] = 1;
for (int i = 2; i <= n; i++){
if (!vis[i]) {// 没有遇到过,一定是最小质因数
prime[cnt++] = i;
phi[i] = i - 1;// 质数n的欧拉函数是n-1
}
for (int j = 0; i * prime[j] <= n; j++){
int m = i * prime[j];
vis[m] = 1;// 筛掉合数
// 以下公式可看证明
if (i % prime[j] == 0){
phi[m] = prime[j] * phi[i];
break;
}
else{
phi[m] = (prime[j] - 1) * phi[i];
}
}
sum[i] = sum[i - 1] + phi[i];
}
cout << sum[n] << endl;
}
P2158 [SDOI2008] 仪仗队
题目描述
作为体育委员,C 君负责这次运动会仪仗队的训练。仪仗队是由学生组成的 \(N \times N\) 的方阵,为了保证队伍在行进中整齐划一,C 君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图)。
现在,C 君希望你告诉他队伍整齐时能看到的学生人数。
输入格式
一行,一个正整数 \(N\)。
输出格式
输出一行一个数,即 C 君应看到的学生人数。
输入输出样例 #1
输入 #1
4
输出 #1
9
说明/提示
对于 \(100 \%\) 的数据,\(1 \le N \le 40000\)。
算法
这里利用了线性筛求欧拉函数的方法。
这道题一看好像是点到点进行模拟,实际上并非这么麻烦。
那么怎么讲这道题与欧拉函数扯上关系呢?关键在于 \(gcd(a,b)=1\) 。
其中 \(a\) 为水平距离,\(b\) 为竖直距离,我们不难发现,能看见的点 \(a\) 和 \(b\) 一定互质。
给定方阵人数 \(n\) ,如果 \(n=1\) ,结果即为0。
这里要查询可看到的人数,实际上就是查询有多少互质的 \(a\) 和 \(b\) ,这里需要对 \(1\sim n\) 的欧拉函数求和,也就是计算不同的 \(a\) (或 \(b\))有多少 \(b\)(或 \(a\))互质,记作 \(sum[n]\) 。
观察规律,可以发现结果即为 \(2\times sum[n-1]+1\) 。
因为两个数互质就必须排除1和自身。对于不同的 \(a\) 和 \(b\) ,计算范围只有 \(2\sim n-1\) 。考虑对称性,共有 \(2\times sum[n-1]\) 个情况。最后考虑对角线上的,结果再加个1,即为最终答案。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 4e4 +5;
int vis[N];
int p[N], cnt;
int phi[N];
int sum[N];
void solve(int n)
{
phi[1] = 1;
sum[1] = 1;
for (int i = 2; i <= n; i++){
if (!vis[i]){
vis[i] = 1;
p[cnt++] = i;
phi[i] = i - 1;
}
for (int j = 0; p[j] * i <= n; j++){
int m = p[j] * i;
vis[m] = 1;
if (i % p[j] == 0){
phi[m] = p[j] * phi[i];
break;
}
else phi[m] = (p[j] - 1) * phi[i];
}
sum[i] = sum[i - 1] + phi[i];
}
if (n == 1) cout << 0 << endl;
else cout << 2 * sum[n - 1] + 1 << endl;
}
int main()
{
int n; cin >> n;
solve(n);
return 0;
}