【7】欧拉函数学习笔记
w9095
·
·
算法·理论
前言
从 这篇博客 中分离出来的。
欧拉函数是一个常用且好用的数论函数,有许多神奇的性质。
欧拉函数
欧拉函数:对正整数 n ,欧拉函数是小于 n 的正整数中与 n 互质的数的数目,记作 \varphi(n) 。
欧拉函数的性质:
1$:$\varphi(1)=1
$3$:$\varphi(p^k)=p^k-p^{k-1}=(p-1)*p^{k-1}
4$:欧拉函数是**积性函数**,若 $m,n$ 互质则有 $\varphi(m\times n)=\varphi(m)\times \varphi(n)
$$\varphi(n)=n\times\prod_{i=1}^r(1-{\frac 1 {p_{i}}})$$
对于性质 $5$,我们可以感性理解。就相当于枚举 $n$ 的每个质因数,把小于 $n$ 且有这个因数的数除去,剩下的就是互质的数。
依据性质 $5$,我们可以得出单个求欧拉函数的算法。
对 $n$ 进行质因数分解,按照性质 $5$ 的公式进行计算。实际上就是把式子用代码实现了而已。时间复杂度 $O(\sqrt{n})$。
```cpp
long long phi(long long x)
{
long long ans=x;
for(long long i=2;i*i<=x;i++)
if(x%i==0)
{
ans=ans/i*(i-1);
while(x%i==0)x/=i;
}
if(x!=1)ans=ans/x*(x-1);
return ans;
}
```
我们可以在线性筛的过程中,线性预处理出 $1\sim n$ 个数的欧拉函数值。
对于性质 $4$,我们知道欧拉函数是积性函数,所以如果我们知道了 $\varphi(n)$ 的值,并知道一个 $n$ 中不存在的质数 $m$,这两个数就一定互质,就可以用 $\varphi(n)\times \varphi(m)$ 得到 $mn$ 的欧拉函数值。
根据性质 $2$,$\varphi(mn)=\varphi(n)\times(m-1)$。
在线性筛的过程中,我们有时确实会出现这种情况。当我们由点 $n$ 乘以其中不存在的质因数 $m$ 筛掉点 $mn$ 时,可以顺便求出 $\varphi(mn)$。但如果不互质,也就是点 $n$ 中存在质因数 $m$,我们直接将 $\varphi(n)$ 乘以 $m$ 即可得到 $\varphi(mn)$。根据性质 $5$,这个 $m$ 并没有导致出现新的质因数,所以乘积式的后一项累乘式结果不变,但是前面的 $n$ 变成了 $mn$,所以总体就乘以了 $m$。这种情况下,线性筛对点 $n$ 的扩展也刚好结束。
由于我们从小到大枚举,且递推式中没有出现后面的值,每一次都知道 $\varphi(n)$,满足递推的条件。时间复杂度 $O(n)$,均摊 $O(1)$。
```cpp
void linear(long long n)
{
a[1]=1;phi[1]=1;
for(long long i=2;i<=n;i++)
{
if(!a[i])pri[++cnt]=i,phi[i]=i-1;
for(long long j=1;j<=cnt&&i*pri[j]<=n;j++)
{
a[i*pri[j]]=1;
phi[i*pri[j]]=phi[i]*(pri[j]-1);
if(i%pri[j]==0)
{
phi[i*pri[j]]=phi[i]*pri[j];
break;
}
}
}
}
```
### 重要性质
$$\sum_{d\mid n}\varphi(d)=n$$
感性理解,记忆即可,证明我不会。
这个性质常用来简化式子,优化复杂度。多用于处理 $\gcd$ 相关问题。例如下面这个式子:
$$\sum_{i=1}^n\sum_{j=1}^n\gcd(i,j)$$
求解这个式子,时间复杂度为 $O(n^2)$。
套用性质,得到这个式子:
$$\sum_{i=1}^n\sum_{j=1}^n\sum_{d\mid gcd(i,j)}\varphi(d)$$
将含 $\gcd$ 的式子按照 $\gcd$ 的性质(若 $d\mid\gcd(x,y)$,则 $d\mid x,d\mid y$)展开,得到:
$$\sum_{i=1}^n\sum_{j=1}^n\sum_{d\mid i,d\mid j}\varphi(d)$$
交换枚举顺序,我们先枚举 $d$,问题就变成了求 $d$ 等于某值时满足条件的 $i,j$ 的数量,用这个数量乘以 $\varphi(d)$ 就能求出这一部分的答案,最后累加即可。$n$ 以内有 $\lfloor\frac{n}{d}\rfloor$ 个数满足 $ d\mid i$,因为每 $d$ 个就有一个数满足条件。$j$ 的数量也是同理。根据乘法原理,总共有 $\lfloor\frac{n}{d}\rfloor^{2}$ 个数满足条件,所以得到:
$$\sum_{d=1}^n\varphi(d)\lfloor\frac{n}{d}\rfloor^{2}$$
时间复杂度为 $O(n)$。
### 例题
例题 $1$ :
[UVA10179 Irreducable Basic Fractions](https://www.luogu.com.cn/problem/UVA10179)
根据约分的定义,我们发现分母为 $n$ 的不可约简单分数的数量就是与 $n$ 互质的小于 $n$ 的数的数量。这样就转化为了欧拉函数模板题,每次询问单独求解即可。
```cpp
#include <bits/stdc++.h>
using namespace std;
int n,a;
bool flag=0;
int eulerfun(int a)
{
int ans=a;
for(int i=2;i*i<=a;i++)
if(a%i==0)
{
ans=ans/i*(i-1);
while(a%i==0)
a/=i;
}
if(a!=1)ans=ans/a*(a-1);
return ans;
}
int main()
{
while(scanf("%d",&a)!=0)
{
if(a==0)return 0;
if(flag)printf("%d\n",eulerfun(a));
else printf("%d\n",eulerfun(a));
flag=1;
}
return 0;
}
```
例题 $2$ :
[P2158 [SDOI2008] 仪仗队](https://www.luogu.com.cn/problem/P2158)
由我们生活中的经验或者看图说话,所以如果一个人在点 $(x,y)$,如果 $\gcd(x,y)=1$,他就能被看到。
我们先假定 $x\lt y$,则问题转化为了小于 $n$ 的正整数中与 $n$ 互质的数的数量,这就是欧拉函数的定义。我们线性处理出 $1\sim n$ 的欧拉函数值,然后求和,就得出了这一部分的答案。
由于 $x$ 可能大于 $y$,我们也需要考虑。思考之后我们发现只需要把上一部分中的 $x,y$ 互换即可。上一部分中每一个满足条件的数值都对应这个部分中的一个数值,答案需要乘以 $2$。
当然,也需要考虑 $x=y$ 的情况。很显然,这种情况下只有 $(1,1)$ 会被看到,答案还需要加 $1$。
注意特判 $n=1$ 的情况。
```cpp
#include <bits/stdc++.h>
using namespace std;
int n,pri[1000001],f[1000001];
bool a[1000001];
int linear(bool a[],int pri[],int n)
{
int cnt=0;
a[1]=1;f[1]=1;
for(int i=2;i<=n;i++)
{
if(!a[i])
{
pri[cnt++]=i;
f[i]=i-1;
}
for(int j=0;pri[j]<=n/i;j++)
{
a[i*pri[j]]=1;
if(i%pri[j]==0)
{
f[i*pri[j]]=f[i]*pri[j];
break;
}
f[i*pri[j]]=f[i]*(pri[j]-1);
}
}
return cnt;
}
int main()
{
long long ans=0;
scanf("%d",&n);
if(n==1)
{
printf("0");
return 0;
}
linear(a,pri,n-1);
for(int i=1;i<=n-1;i++)
ans+=f[i];
printf("%lld",ans*2+1);
return 0;
}
```
例题 $3$ :
[P2398 GCD SUM](https://www.luogu.com.cn/problem/P2398)
同重要性质部分,不多赘述。
```cpp
#include <bits/stdc++.h>
using namespace std;
long long n,pri[100000001],f[100000001];
bool a[100000001];
int linear(bool a[],long long pri[],long long n)
{
int cnt=0;
a[1]=1;f[1]=1;
for(int i=2;i<=n;i++)
{
if(!a[i])
{
pri[cnt++]=i;
f[i]=i-1;
}
for(int j=0;pri[j]<=n/i;j++)
{
a[i*pri[j]]=1;
if(i%pri[j]==0)
{
f[i*pri[j]]=f[i]*pri[j];
break;
}
f[i*pri[j]]=f[i]*(pri[j]-1);
}
}
return cnt;
}
int main()
{
long long ans=0;
scanf("%lld",&n);
linear(a,pri,n);
for(int i=1;i<=n;i++)
ans+=(f[i]*(n/i)*(n/i));
printf("%lld",ans);
return 0;
}
```
例题 $4$ :
[P2303 [SDOI2012] Longge 的问题](https://www.luogu.com.cn/problem/P2303)
很套路的进行转化,如下:
$$\sum_{i=1}^{n}\gcd(i,n)$$
套用重要性质,得到这个式子:
$$\sum_{i=1}^{n}\sum_{d\mid \gcd(i,n)}\varphi(d)$$
将含 $\gcd$ 的式子按照 $\gcd$ 的性质(若 $d\mid\gcd(x,y)$,则 $d\mid x,d\mid y$)展开,得到:
$$\sum_{i=1}^{n}\sum_{d\mid i,d\mid n}\varphi(d)$$
交换枚举顺序,我们先枚举 $d$,问题就变成了求 $d$ 等于某值时满足条件的 $i$ 的数量,用这个数量乘以 $\varphi(d)$ 就能求出这一部分的答案,最后累加即可。$n$ 以内有 $\lfloor\frac{n}{d}\rfloor$ 个数满足 $ d\mid i$,因为每 $d$ 个就有一个数满足条件。由于还有额外条件 $d\mid n$,所以我们把这个条件放在外层,得到:
$$\sum_{d\mid n}\varphi(d)\lfloor\frac{n}{d}\rfloor$$
我们可以在枚举因数的同时满足 $d\mid n$ 的条件,只需要枚举 $\sqrt{n}$ 之内的数。如果 $\frac{n}{d}$ 不等于 $d$,那么就再计算一次 $\frac{n}{d}$ 的情况。由于 $d$ 的范围很大,不能线性筛,所以每次单独计算欧拉函数值。时间复杂度为 $O(\text{因数个数}\times \sqrt{n})$。
```cpp
#include <bits/stdc++.h>
using namespace std;
long long n,ans=0;
long long phi(long long x)
{
long long ans=x;
for(long long i=2;i*i<=x;i++)
if(x%i==0)
{
ans=ans/i*(i-1);
while(x%i==0)x/=i;
}
if(x!=1)ans=ans/x*(x-1);
return ans;
}
int main()
{
scanf("%lld",&n);
for(long long i=1;i*i<=n;i++)
if(n%i==0)
{
ans+=phi(i)*(n/i);
if(n/i!=i)ans+=phi(n/i)*(n/(n/i));
}
printf("%lld\n",ans);
return 0;
}
```
例题 $5$ :
[P1447 [NOI2010] 能量采集](https://www.luogu.com.cn/problem/P1447)
由我们生活中的经验或者看图说话,我们得到答案为这个式子:
$$2\sum_{i=1}^{n}\sum_{j=1}^{m}\gcd(i,j)-mn$$
非常套路的进行转化,套用重要性质,得到这个式子:
$$2\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{d\mid \gcd(i,j)}\varphi(d)-mn$$
将含 $\gcd$ 的式子按照 $\gcd$ 的性质(若 $d\mid\gcd(x,y)$,则 $d\mid x,d\mid y$)展开,得到:
$$2\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{d\mid i,d\mid j}\varphi(d)-mn$$
交换枚举顺序,我们先枚举 $d$,问题就变成了求 $d$ 等于某值时满足条件的 $i,j$ 的数量,用这个数量乘以 $\varphi(d)$ 就能求出这一部分的答案,最后累加即可。$n$ 以内有 $\lfloor\frac{n}{d}\rfloor$ 个数满足 $ d\mid i$,因为每 $d$ 个就有一个数满足条件。$j$ 的数量也是同理,有 $\lfloor\frac{m}{d}\rfloor$ 个。根据乘法原理,总共有 $\lfloor\frac{n}{d}\rfloor\lfloor\frac{m}{d}\rfloor$ 个数满足条件。注意这里不需要枚举大于 $m,n$ 中任意一值的部分,因为向下取整,这一部分必定为 $0$,所以得到:
$$=2\sum_{d=1}^{\min(n,m)}\varphi(d)\lfloor\frac{n}{d}\rfloor\lfloor\frac{m}{d}\rfloor-mn$$
```cpp
#include <bits/stdc++.h>
using namespace std;
long long n,m,pri[1000010],phi[1000010],cnt,ans;
bool a[1000010];
void linear(long long n)
{
a[1]=1;phi[1]=1;
for(long long i=2;i<=n;i++)
{
if(!a[i])pri[++cnt]=i,phi[i]=i-1;
for(long long j=1;j<=cnt&&i*pri[j]<=n;j++)
{
a[i*pri[j]]=1;
phi[i*pri[j]]=phi[i]*(pri[j]-1);
if(i%pri[j]==0)
{
phi[i*pri[j]]=phi[i]*pri[j];
break;
}
}
}
}
int main()
{
scanf("%lld%lld",&n,&m);
linear(max(n,m));
for(int i=1;i<=min(n,m);i++)ans+=phi[i]*(n/i)*(m/i);
printf("%lld\n",ans*2-m*n);
return 0;
}
```
### 后记
熬到半夜准备打CF,结果一看时间是昨天,一气之下写的博客。