这道题是欧拉函数的使用,这里简要介绍下欧拉函数。
欧拉函数定义为:对于正整数n,欧拉函数是指不超过n且与n互质的正整数的个数。
欧拉函数的性质:1.设n = p1a1p2a2p3a3p4a4...pkak为正整数n的素数幂分解,那么φ(n) = n·(1-1/p1)·(1-1/p2)·(1-1/p3)···(1-1/pk)
2.如果n是质数,则φ(n) = n-1; 反之,如果p是一个正整数且满足φ(p)=p-1,那么p是素数。
3.设n是一个大于2 的正整数,则φ(n)是偶数
4.当n为奇数时,有φ(2n)=φ(n)
5.设m和n是互质的正整数,那么φ(mn)=φ(m)φ(n)
(1)可以根据性质1,写出计算欧拉函数值的程序: 复杂度为O(√n)
1 //直接求解欧拉函数 2 int euler(int n){ //返回euler(n) 3 int res=n; 4 for(int i=2;i*i<=n;i++){ 5 if(n%i==0){ 6 res=res/i*(i-1);//先进行除法是为了防止中间数据的溢出 7 while(n%i==0) n/=i; 8 } 9 } 10 if(n>1) res=res/n*(n-1); 11 return res; 12 }
(2)上面这种写法中,在for循环中选择i时,是顺序选择的。事实上性质1 中的p1、p2、p3、p4、...pk都是质数。如果在选择时,直接选择质数进行判断,那结果会优化很多。
可以先把50000以内的素数用筛法选出来并保存,以方便欧拉函数使用。这样,在不考虑筛法的时间复杂度,而单看欧拉函数,其复杂度变为O(x),x为√n以内素数的个数。
1 #include <cstring> 2 bool boo[50000];int p[20000]; 3 4 void prim(){ 5 //线性筛素数 6 memset(boo,0,sizeof(boo)); 7 boo[0]=boo[1]=1; 8 int k=0; 9 for(int i=2;i<50000;i++){ 10 if(!boo[i]) p[k++]=i; 11 for(int j=0;j<k&&i*p[j]<50000;j++){ 12 boo[i*p[j]] = 1; 13 count++; 14 if(!(i%p[j])) break; 15 } 16 } 17 18 } 19 20 int phi(int n){ 21 int rea = n; 22 for(int i=0;p[i]*p[i]<=n;i++ ){ //对一些不是素数的可不用遍历 23 if(n%p[i]==0){ 24 rea = rea-rea/p[i]; 25 while(n%p[i]==0) n/=p[i]; 26 } 27 } 28 if(n>1) rea-=rea/n; 29 return rea; 30 }
(3)递推求欧拉函数
如果频繁的要使用欧拉函数值,就需要预先打表。复杂度约为O(nlnn)
1 //递推法打欧拉函数表 2 #define Max 1000001 3 int phi[Max]; 4 void Init(){ 5 for(int i=1;i<=Max;i++) phi[i]=i; 6 for(int i=2;i<=Max;i+=2) phi[i]/=2; 7 for(int i=3;i<=Max;i+=2) 8 if(phi[i]==i) 9 for(int j=i;j<=Max;j+=i) 10 phi[j]=phi[j]/i*(i-1);//先进行除法是为了防止中间数据的溢出 11 }
应用:NYOJ 998 http://acm.nyist.net/JudgeOnline/problem.php?pid=998
这道题的精华如何将符合条件的gcd(x,n)表达出来:见代码 d*Euler(n/d) 中为什么乘以d的解释 。
然后是遍历一遍小于n的数,测试每个符合的数加起来即可。其实还可以更快,观察发现,在能够整除n的i里面,相对应的n/i也有相似的性质,这样以来只需要遍历到sqrt(n)即可。
网络摘抄代码如下:
1 2 #include<iostream> 3 #include<cstdio> 4 using namespace std; 5 6 typedef long long LL; 7 LL Euler(LL n){ 8 LL ans = n; 9 for(int i = 2; i * i <= n; i++){ 10 if(n % i == 0){ 11 ans = ans / i * (i-1); 12 while(n % i == 0) 13 n /= i; 14 } 15 } 16 if(n > 1) ans = ans / n * (n-1); 17 return ans; 18 } 19 20 int main(){ 21 LL n,m; 22 while(cin>>n>>m){ 23 LL ans = 0; 24 for(int i = 1; i * i <= n; i++){ 25 if(n % i == 0){ 26 if(i >= m){ 27 int d = i; 28 ans += d*Euler(n/d); 29 // 考虑 gcd(x,n) 1=<x<=n 30 //这个的由来是 gcd(x/d,n/d) = 1.如果我们取一个能让n/d取整数的d的取值,于是我们 31 //取到了n%i==0的i,于是 能够满足gcd(x,n) = d 的x的个数为Euler(n/d)个 32 //那么在该gcd = d 的情况下需要加到ans里面的d的个数就是Euler(n/d)个 ,所以有ans+=d*Euler(n/d) 33 34 } 35 if(i * i != n && n / i >= m){ 36 int d = n / i; 37 ans += d*Euler(n/d); 38 } 39 } 40 } 41 cout<<ans<<endl; 42 } 43 return 0; 44 } 45