【7】欧拉函数学习笔记
前言
从 这篇博客 中分离出来的。
欧拉函数是一个常用且好用的数论函数,有许多神奇的性质。
欧拉函数
欧拉函数:对正整数 ,欧拉函数是小于 的正整数中与 互质的数的数目,记作 。
欧拉函数的性质:
:
:( 为质数)
:
:欧拉函数是积性函数,若 互质则有
:对于任意的 (其中 为 的不同素因子),则有
对于性质 ,我们可以感性理解。就相当于枚举 的每个质因数,把小于 且有这个因数的数除去,剩下的就是互质的数。
依据性质 ,我们可以得出单个求欧拉函数的算法。
对 进行质因数分解,按照性质 的公式进行计算。实际上就是把式子用代码实现了而已。时间复杂度 。
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;
}
我们可以在线性筛的过程中,线性预处理出 个数的欧拉函数值。
对于性质 ,我们知道欧拉函数是积性函数,所以如果我们知道了 的值,并知道一个 中不存在的质数 ,这两个数就一定互质,就可以用 得到 的欧拉函数值。
根据性质 ,。
在线性筛的过程中,我们有时确实会出现这种情况。当我们由点 乘以其中不存在的质因数 筛掉点 时,可以顺便求出 。但如果不互质,也就是点 中存在质因数 ,我们直接将 乘以 即可得到 。根据性质 ,这个 并没有导致出现新的质因数,所以乘积式的后一项累乘式结果不变,但是前面的 变成了 ,所以总体就乘以了 。这种情况下,线性筛对点 的扩展也刚好结束。
由于我们从小到大枚举,且递推式中没有出现后面的值,每一次都知道 ,满足递推的条件。时间复杂度 ,均摊 。
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;
}
}
}
}
重要性质
感性理解,记忆即可,证明我不会。
这个性质常用来简化式子,优化复杂度。多用于处理 相关问题。例如下面这个式子:
求解这个式子,时间复杂度为 。
套用性质,得到这个式子:
将含 的式子按照 的性质(若 ,则 )展开,得到:
交换枚举顺序,我们先枚举 ,问题就变成了求 等于某值时满足条件的 的数量,用这个数量乘以 就能求出这一部分的答案,最后累加即可。 以内有 个数满足 ,因为每 个就有一个数满足条件。 的数量也是同理。根据乘法原理,总共有 个数满足条件,所以得到:
时间复杂度为 。
例题
例题 :
UVA10179 Irreducable Basic Fractions
根据约分的定义,我们发现分母为 的不可约简单分数的数量就是与 互质的小于 的数的数量。这样就转化为了欧拉函数模板题,每次询问单独求解即可。
#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;
}
例题 :
由我们生活中的经验或者看图说话,所以如果一个人在点 ,如果 ,他就能被看到。
我们先假定 ,则问题转化为了小于 的正整数中与 互质的数的数量,这就是欧拉函数的定义。我们线性处理出 的欧拉函数值,然后求和,就得出了这一部分的答案。
由于 可能大于 ,我们也需要考虑。思考之后我们发现只需要把上一部分中的 互换即可。上一部分中每一个满足条件的数值都对应这个部分中的一个数值,答案需要乘以 。
当然,也需要考虑 的情况。很显然,这种情况下只有 会被看到,答案还需要加 。
注意特判 的情况。
#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;
}
例题 :
同重要性质部分,不多赘述。
#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;
}
例题 :
很套路的进行转化,如下:
套用重要性质,得到这个式子:
将含 的式子按照 的性质(若 ,则 )展开,得到:
交换枚举顺序,我们先枚举 ,问题就变成了求 等于某值时满足条件的 的数量,用这个数量乘以 就能求出这一部分的答案,最后累加即可。 以内有 个数满足 ,因为每 个就有一个数满足条件。由于还有额外条件 ,所以我们把这个条件放在外层,得到:
我们可以在枚举因数的同时满足 的条件,只需要枚举 之内的数。如果 不等于 ,那么就再计算一次 的情况。由于 的范围很大,不能线性筛,所以每次单独计算欧拉函数值。时间复杂度为 。
#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;
}
例题 :
由我们生活中的经验或者看图说话,我们得到答案为这个式子:
非常套路的进行转化,套用重要性质,得到这个式子:
将含 的式子按照 的性质(若 ,则 )展开,得到:
交换枚举顺序,我们先枚举 ,问题就变成了求 等于某值时满足条件的 的数量,用这个数量乘以 就能求出这一部分的答案,最后累加即可。 以内有 个数满足 ,因为每 个就有一个数满足条件。 的数量也是同理,有 个。根据乘法原理,总共有 个数满足条件。注意这里不需要枚举大于 中任意一值的部分,因为向下取整,这一部分必定为 ,所以得到:
#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,结果一看时间是昨天,一气之下写的博客。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探