欧拉函数与积性函数
Part 1:欧拉函数及其性质
-
定义:欧拉函数
表示小于等于 ,且与 互质的正整数的个数。 -
公式:
若在算数基本定理中, ( 为质数),则由容斥原理:关于此公式的证明,读者可自行推导
一些性质
-
若
是质数,则 -
若
,且 为质数,则 -
若
是质数,且 -
-
,其中
Part 2:积性函数
定义
若一个定义在正整数域上的函数
特别地,当
常见完全积性函数:
常见积性函数
- 欧拉函数
- 莫比乌斯函数
- 除数函数
- 除数函数
的约数个数 所有约数的和
一些乘积结论
除数函数的计算
对于
-
记
为 的最小质因子,有: -
Part 3:欧拉函数例题
【模板题】求 φ(n)
解题思路
- 我们已知
的计算公式,所以只需对 进行质因数分解即可
代码
#include<bits/stdc++.h>
using namespace std;
const int N=100001;
int n;
int GetPhi(int n)
{
int ans=n;
for(int i=2; i*i<=n; i++) //分解质因数
{
if(n%i==0)
{
ans=ans*(i-1)/i; //计算公式
while(n%i==0)
n/=i;
}
}
if(n>1) //还剩下一个较大的质数
ans=ans*(n-1)/n;
return ans;
}
int main()
{
cin>>n;
cout<<GetPhi(n);
return 0;
}
【模板题】求 φ(1) ~ φ(n)
解题思路
-
由于欧拉函数是积性函数,所以我们考虑用线性筛求解
-
从欧拉函数性质
出发,在线性筛中,每个合数 都会被它最小的质因子 筛去,此时我们就可以用 或 和 来推出 ,具体是 还是 ,要看 ,即我们外层循环的 是否是 的倍数;而对于每个质数 ,由性质 可得:
代码
#include<bits/stdc++.h>
using namespace std;
const int N=100001;
int n,phi[N],v[N],prime[N],tot;
void GetPhi(int n)
{
phi[1]=1;
for(int i=2; i<=n; i++)
{
if(!v[i])
{
v[i]=i;
prime[++tot]=i;
phi[i]=i-1;
}
for(int j=1; j<=tot; j++)
{
if(prime[j]>v[i] || prime[j]>n/i)
break;
v[i*prime[j]]=prime[j];
if(i%prime[j]==0)
phi[i*prime[j]]=prime[j]*phi[i];
else
phi[i*prime[j]]=(prime[j]-1)*phi[i];
}
}
}
int main()
{
cin>>n;
GetPhi(n);
for(int i=1; i<=n; i++)
cout<<phi[i]<<" ";
return 0;
}
【练习题】公约数的和
(题目传送门)
题目大意
给定
其中
解题思路
-
我们记
-
改写一下可得:
-
那么我们枚举最大公约数
,设 表示有多少对 使得 ,那么就有 -
进一步思考,若整数对
满足 ,则有: 。 -
设
,下面分 种情况讨论对于每一个 ,有多少个 满足 :1、若
,则 。又因为 ,所以 。那么满足 的 的数量就为 ~ 里与 互质的数的个数,即 。2、若
,则 ,那么令 ,那么此时的答案即为第 1 种情况中已求出的满足 的 的数量。所以答案为 。因此,每一个 都会被算 遍3、若
,则 ,此时 的取值很明显只有 种 -
综上,
-
在求
前,可对 进行前缀和的预处理,节省时间
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2000010;
int n,phi[N],v[N],prime[N],tot;
long long ans,sum[N],f[N];
void GetPhi(int n) //求phi[1~n]
{
phi[1]=1;
for(int i=2; i<=n; i++)
{
if(!v[i])
{
v[i]=i;
prime[++tot]=i;
phi[i]=i-1;
}
for(int j=1; j<=tot; j++)
{
if(prime[j]>v[i] || prime[j]>n/i)
break;
v[i*prime[j]]=prime[j];
if(i%prime[j]==0)
phi[i*prime[j]]=prime[j]*phi[i];
else
phi[i*prime[j]]=(prime[j]-1)*phi[i];
}
}
for(int i=1; i<=n; i++) //前缀和预处理
sum[i]=sum[i-1]+(long long)phi[i];
}
int main()
{
cin>>n;
GetPhi(n);
for(int i=1; i<=n; i++)
f[i]=sum[n/i]*2-1,ans+=f[i]*(long long)i; //求f和ans
ans=(ans-(long long)(1+n)*n/2)/2; //最后一步
cout<<ans;
return 0;
}
【练习题】GCD(x,n)>=m
题目大意
给定整数
解题思路
-
记
,那么由定义得: 。因此我们考虑去枚举 的约数,并舍去其中小于 的约数,那么剩下的数便可以作为 的取值。 -
对于大于等于
的约数 ,由上一题的解题思路可知: 且 ,所以答案便为
代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
int T,n,m;
LL ans;
LL GetPhi(int n) //求phi[n]
{
LL ans=(LL)n;
for(int i=2; i*i<=n; i++)
{
if(n%i==0)
{
ans=ans*(LL)(i-1)/i;
while(n%i==0)
n/=i;
}
}
if(n>1)
ans=ans*(LL)(n-1)/n;
return ans;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
ans=0;
for(int i=1; i*i<=n; i++) //枚举约数
{
if(n%i==0)
{
if(i>=m) //约数i
ans=ans+(LL)GetPhi(n/i);
if(i!=n/i && n/i>=m) //由于约数成对出现,所以n/i也是n的约数,注意要避免完全平方数的情况
ans=ans+(LL)GetPhi(i);
}
}
printf("%lld\n",ans);
}
return 0;
}
【练习题】非互质数求和
题目大意
如果一个正整数
给定一个正整数
这里互质的定义:如果
答案模
解题思路
-
考虑到正面求解较难,我们可以考虑求答案的补集,即求所有与
互质的数的和 -
在这我们先引入一个结论:
这个结论我们最后会来证明。
那么如果
与 互质,由结论就可知 也与 互质 -
设集合
为与 互质的数的集合。则集合元素个数为 ,集合元素分别为 。由刚刚的结论可知 ,这样的数对共有 个,所以 -
最后答案即为
关于引入结论的证明
-
设
,则有 且 ,那么 -
假设
,则 。所以可设 ,那么可推出 ,易知 ,所以 ,因此假设矛盾 -
综上,
代码
#include<bits/stdc++.h>
using namespace std;
const long long MOD=1000000007;
long long n;
long long GetPhi(long long m) //求phi[n]
{
long long ans=m;
for(int i=2; i*i<=m; i++)
{
if(m%i==0)
{
ans=ans*(i-1)/i;
while(m%i==0)
m/=i;
}
}
if(m>1)
ans=ans*(m-1)/m;
return ans;
}
int main()
{
while(scanf("%lld",&n))
{
if(n==0)
break;
printf("%lld\n",(n*(n-1)/2-n*GetPhi(n)/2)%MOD);
}
return 0;
}
【练习题】USB的数学题
题目大意
求
解题思路
-
设
,则满足 的 的个数就为 。所以 可写成 -
再看一眼数据范围,显然对于每组测试数据的每个
,都进行根号级别的枚举是无法拿到满分的,所以我们要思考 所具有的其它性质——没错,积性函数! -
是积性函数是很好证明的。设 为互质的两个正整数。则 。那么 。由于 与 互质,所以 与 也是互质的,再加上 是积性函数,所以因此,
是积性函数 -
一开始我们已经说过,所有的积性函数都可以用线性筛求解。本题我们分
种情况对 的递推进行讨论:1、当
时, 的最小质因子为 , 与 互质,所以 可以由 得到。这是最简单的一种情况。2、当
时, 的最小质因子为 ,但 与 不互质,所以此时无法直接递推。因为 ,所以我们需要令 ,这样 与 一定是互质的,再通过 得到 。因此,我们需要记录每一个数的最小质因子的最高次幂的值,记为 。对于这种情况,我们就可以令 ,再通过 得到3、当
时, ,就会产生 这样的情况,所以我们还需要记录 的最小质因子的指数,记为 。那么当 时,我们可以暴力求解 。显然我们有 ( 是当 的情况)。由 的计算公式,我们就可以得到:我们再通过一个简单的前缀积处理,就可以暴力的求解这种情况的
代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=10000010;
int T,n;
int v[N],prime[N],tot;
LL f[N],highPow[N],mi[N],tmp[N];
void prework(int n)
{
f[1]=1;
for(int i=2; i<=n; i++)
{
if(!v[i])
{
v[i]=i;
prime[++tot]=i;
f[i]=(LL)(i-1)*i+1;
highPow[i]=i;
mi[i]=1;
}
for(int j=1; j<=tot; j++)
{
if(prime[j]>v[i] || prime[j]>n/i)
break;
int x=i*prime[j];
v[x]=prime[j];
if(i%prime[j]!=0) //第1种情况(由于prime[j]是质数,所以i与prime[j]互质当且仅当i不是prime[j]的倍数或约数)
{
highPow[x]=prime[j];
mi[x]=1;
f[x]=f[i]*f[prime[j]];
}
else
{
highPow[x]=highPow[i]*prime[j];
mi[x]=mi[i]+1;
int a=highPow[x],b=x/a;
if(b==1) //第3种情况
{
tmp[0]=1;
for(int i=1; i<=mi[x]; i++)
tmp[i]=tmp[i-1]*(LL)prime[j]; //前缀积处理prime[j]的幂
for(int i=1; i<=mi[x]; i++)
f[x]+=tmp[i]*tmp[i-1]*(LL)(prime[j]-1); //暴力求解
f[x]++; //加上d=1的情况
}
else //第2种情况
f[x]=f[a]*f[b];
}
}
}
}
int main()
{
prework(N-10);
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
printf("%lld\n",f[n]);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?