欧拉函数求法总结及相关例题

注意:本博客并非写给欧拉函数的初学者,而是为已经学会欧拉函数的OIer们提供一点总结。


题型一:求解单个数的欧拉函数值

模板题:POJ2407

方法:根据公式求解

根据欧拉函数的通项公式 φ ( x ) = x ∏ i = 1 n ( 1 − 1 p i ) \varphi(x)=x\prod_{i=1}^{n}(1-\frac{1}{p_{i}}) φ(x)=xi=1n(1pi1)通过对数x进行类似质因数分解的操作完成单个欧拉函数值的计算。复杂度 Θ ( x ) \Theta(\sqrt{x}) Θ(x )
代码:

#include<cstdio>
#include<algorithm>
#include<cmath>
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
using namespace std;
int n;
int main(){
	while(scanf("%d",&n)==1&&n!=0){
		int ans=n,lim=sqrt(n);
		rep(i,2,lim) if(n%i==0){
			ans=ans/i*(i-1);//先除再乘,避免爆int
			while(n%i==0) n/=i; 
		}
		if(n>1) ans=ans/n*(n-1);
		printf("%d\n",ans);
	}
	return 0;
}
题型二:求解一段数的欧拉函数值

模板题:HDU2824

方法一:利用埃拉托斯特尼筛求解一段数的欧拉函数值

当需要求解一段连续的几十万个数的欧拉函数时,使用第一种方法基本上就TLE了,我们利用埃氏筛在用素数p筛除其他数的同时,计算p对每一个被筛的数欧拉函数的贡献,复杂度是 Θ ( n l o g l o g n ) \Theta(nloglogn) Θ(nloglogn)。具体见代码。

#include<bits/stdc++.h>
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
using namespace std;
const int inf=1e9+10,N=3e6+100;
int a,b,phi[N+1];
bool prime[N+1];//false为素数,true为合数。 
long long ans;
inline void Eratos_to_Euler(){
	rep(i,2,N) phi[i]=i;
	rep(i,2,N)
		if(prime[i]==false)
			for(int j=i;j<=N;j+=i)
				phi[j]=phi[j]/i*(i-1),prime[j]=true;
}
int main(){
	Eratos_to_Euler();
	while(scanf("%d%d",&a,&b)==2){
		long long ans=0;
		rep(i,a,b) ans+=phi[i];
		printf("%lld\n",ans);
	}
	return 0;
}

方法二:利用欧拉筛求解一段数的欧拉函数值

同样也是在筛除其他数的同时算出被筛数的欧拉函数,但并不是像埃氏筛那样子使用每一个素数计算对被筛数的贡献——那样复杂度是 Θ ( n l o g l o g n ) \Theta(nloglogn) Θ(nloglogn)的。而是通过欧拉函数的性质(也可以理解是通项)来推出被筛数的欧拉函数。具体如下:
在欧拉筛中,当 p j p_{j} pj整除 i i i时, φ ( p j ∗ i ) = φ ( i ) ∗ p j \varphi(p_{j}*i)=\varphi(i)*p_{j} φ(pji)=φ(i)pj
否则 φ ( p j ∗ i ) = φ ( i ) ∗ ( p j − 1 ) \varphi(p_{j}*i)=\varphi(i)*(p_{j}-1) φ(pji)=φ(i)(pj1)
证明:
以下是一种我认为相当直观好理解的证明方法(自己想到的):
1.当 p j p_{j} pj整除 i i i时,也就是 i i i含有 p j p_{j} pj这个素因子,那么被筛数 a = i ∗ p j a=i*p_{j} a=ipj的不同的素因子个数完全没有改变,在欧拉函数的通项公式中:
φ ( x ) = x ∏ i = 1 n ( 1 − 1 p i ) \varphi(x)=x\prod_{i=1}^{n}(1-\frac{1}{p_{i}}) φ(x)=xi=1n(1pi1)
我们可以知道 a a a i i i ∏ i = 1 n ( 1 − 1 p i ) \prod_{i=1}^{n}(1-\frac{1}{p_{i}}) i=1n(1pi1)这一部分是一模一样的。因此 φ ( a ) \varphi(a) φ(a)的值与 φ ( i ) \varphi(i) φ(i)的值区别只在于 x x x,而 a = i ∗ p j a=i*p_{j} a=ipj,所以差的是一个x中的 p j p_{j} pj,因此当 p j p_{j} pj整除 i i i时, φ ( p j ∗ i ) = φ ( i ) ∗ p j \varphi(p_{j}*i)=\varphi(i)*p_{j} φ(pji)=φ(i)pj

2.根据同样的思路,当 p j p_{j} pj不整除 i i i时,也就是 i i i不含有 p j p_{j} pj这个素因子。
那么被筛数 a = i ∗ p j a=i*p_{j} a=ipj的不同的素因子个数只改变增加了一个,那就是 p j p_{j} pj.
因此在 φ ( a ) = φ ( i ) ∗ p j \varphi(a)=\varphi(i)*p_{j} φ(a)=φ(i)pj的基础上,还要再乘上一个值,那就是 p j − 1 p j \frac{p_{j}-1}{p_{j}} pjpj1.
φ ( a ) = φ ( i ) ∗ p j ∗ p j − 1 p j = φ ( i ) ∗ ( p j − 1 ) \varphi(a)=\varphi(i)*p_{j}*\frac{p_{j}-1}{p_{j}}=\varphi(i)*(p_{j}-1) φ(a)=φ(i)pjpjpj1=φ(i)(pj1)。证毕。

复杂度 Θ ( n ) \Theta(n) Θ(n)
模板题同上:

#include<bits/stdc++.h>
#define rep(i,l,r) for(int i=(l);i<=(r);i++)
using namespace std;
typedef long long ll;
const int inf=1e9+10,N=3e6+100;
int a,b,phi[N+1],v[N],prime[N],len;
inline void Euler_to_Euler(){
	rep(i,2,N){
		if(v[i]==0){
			v[i]=i;
			phi[i]=i-1;
			prime[++len]=i;
		}
		rep(j,1,len){
			if(prime[j]>N/i||prime[j]>v[i]) break;
			//最好写成 prime[j]>N/i,而不是i*prime[j]>N,避免爆int。
			v[i*prime[j]]=prime[j];//筛 
			if(i%prime[j]==0) phi[i*prime[j]]=phi[i]*prime[j];//计算欧拉函数 
				else phi[i*prime[j]]=phi[i]*(prime[j]-1);
		}
	}
}
int main(){
	Euler_to_Euler();
	while(scanf("%d%d",&a,&b)==2){
		long long ans=0;
		rep(i,a,b) ans+=phi[i];
		printf("%lld\n",ans);
	}
	return 0;
}

觉得写得好的话,就点个赞让我知道一下呗~

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值