欧拉函数与欧拉函数的递推总结+例题POJ2478

来源:互联网 发布:演技 知乎 编辑:程序博客网 时间:2024/05/22 00:36

先来介绍几个与欧拉函数有关的定理:

定理一:设m与n是互素的正整数,那么

定理二:当n为奇数时,有


因为2n是偶数,偶数与偶数一定不互素,所以只考虑2n与小于它的奇数互素的情况,则恰好就等于n的欧拉函数值。


定理三:设p是素数,a是一个正整数,那么


关于这个定理的证明用到容斥:


由于表示小于互素数的正整数个数,所以用减去与它不互素的数的个数就行了。

那么小于不互素数的个数就是p的倍数个数,有个。所以定理得证。


定理四:设为正整数n的素数幂分解,那么

 


 

这个定理可以根据定理一和定理三证明,其实用到的就是容斥。如果对容斥熟悉,其实完全就可以直接容斥。


定理五:设n是一个正整数,那么

 

 

这个其实可以看莫比乌斯反演就明白了。


定理六:设m是正整数,(a,m)=1,则:是同于方程的解。


定理七:如果n大于2,那么n的欧拉函数值是偶数。

1.求单个数的欧拉函数(白书版本)

int euler_phi(int n){        int ans=n;        for(int i=2;i*i<=n;i++) if(n%i==0)        {            ans=ans/i*(i-1);            while(n%i==0) n/=i;        }        if(n>1) ans=ans/n*(n-1);        return ans;    }}


2.1求1~n中所有数的欧拉phi函数值(这里就不引用白书上的了,太慢了。。。)

void Solve(void){    for(int i=1; i<n; i++)  p[i] = i;    for(int i=2; i<n; i+=2) p[i] >>= 1;    for(int i=3; i<n; i+=2)    {        if(p[i] == i)        {            for(int j=i; j<n; j+=i)                p[j] = p[j] - p[j] / i;        }    }}


算法原理:开始令i的欧拉函数值等于它本身,如果i为偶数,可以利用定理二变为求奇数的。若p是一个正整数满足,那么p是素数,在遍历过程中如果遇到欧拉函数值等于自身的情况,那么说明该数为素数。把这个数的欧拉函数值改变,同时也把能被该素因子整除的数改变。
2.2 求1~n中欧拉函数的改进版(主要看n大不大)

先打个素数表:

bool isprime[n];long long num_prime;void get_prime(){    memset(isprime,true,sizeof(isprime));    isprime[0]=isprime[1]=false;    for(int i=2;i*i<n;i++){        if(isprime[i]){            num_prime++;            for(int j=i*i;j<=n;j+=i)                isprime[i]=false;        }    }}

然后根据递推:

对于任意一个能被n整除的质数,有m = n/p

当m%p == 0 的时候,phi(n) = phi(m)*p

当m%p != 0的时候,phi(n) = phi(m)*(p-1)

long long dp[n]= {0ll};void Solve(void){    get_prime();    for(int i=2; i<n; ++i)    {        if(is_prime[i])            dp[i]=i-1;        else        {            for(int j=0; j<num_prime; ++j)                 if(i%prime[j]==0)                {                    if((i/prime[j])%prime[j]==0)                        dp[i]=dp[i/prime[j]]*prime[j];                    else                        dp[i]=dp[i/prime[j]]*(prime[j]-1);                    break;                }        }    }}


递推式证明:

1)由质因数分解定理得,在n中至少能够分解出一个素数p,导致 n = p*m,那么可以将1~n划分为一个区间的集合{ [m*(i-1) , m*i ] , 1<=i<=p },则对于每个区间[m*(i-1)+1,m*i],其中的每个数可以看做m*(i-1)+j (1<=j<=m)。

2)所以gcd ( m*(i-1)+j , m ) 

= gcd ( m , (m*(i-1)+j)%m ) (根据秦九韶辗转相除法)

=gcd ( m , j )

那么也就是每个区间对应位置的数均与1~m中的某个数gcd相同,互质便是gcd(a,b)==1,所以1~n中与m互质的数有p*phi(m)个。

3)那么我们接下来进行分类讨论:

若m%p == 0 , 那么 m = a*p(a为一个正整数),所以若gcd(b,m) == 1 ,那么 gcd ( b , p ) == 1 , 所以 gcd ( m*p , b ) == 1 , 即gcd ( n , b ) == 1 ,所以在这种情况与m互质的数均与n互质,所以phi(n) = phi(m)*p;

若m%p != 0 , 那么若gcd ( b , m) == 1 , gcd ( b , p ) == p , 那么 gcd ( n , b ) == p,所以1~m与m互质的数为q,若m%p != 0 , 那么与m互质且与p不互质的数是q*p,因此在n当中这样的数有phi(m)个,所以 phi(n) = phi(m)*p - phi(m)


例题:POJ 2478

->题目传送门<-

题意:输入n,求n以内的数的欧拉函数的和(分子分母互素)

2.1版:422MS

#include <stdio.h>#include <iostream>#include <cstring>using namespace std;const int MAX=1000010;int prime[MAX]={0},num_prime=0;//num_pirme记录素数个数bool is_prime[MAX];void GetPrime(){  memset(is_prime,true,sizeof(is_prime));  is_prime[0]=is_prime[1]=false;  for(int i=2;i*i<MAX;i++)  {    if(is_prime[i])    for(int j=i*i;j<=MAX;j+=i)    {      is_prime[j]=false;    }  }}long long p[MAX];void Solve(){    for(int i=1; i<MAX; i++)  p[i] = i;    for(int i=2; i<MAX; i+=2) p[i] >>= 1;    for(int i=3; i<MAX; i+=2)    {        if(p[i] == i)        {            for(int j=i; j<MAX; j+=i)                p[j] = p[j] - p[j] / i;        }    }}int main(void){  int n,ncase=1;  Solve();  while(scanf("%d",&n)==1 && n)  {        long long sum=0;        for(int i=2;i<=n;i++){            sum+=p[i];        }        cout<<sum<<endl;  }  return 0;}



2.2版:157MS

#include <cstdio>#include <iostream>#include <cstring>using namespace stdconst int maxn=1000010;int prime[maxn],num_prime=0;//num_pirme记录素数个数bool is_prime[maxn];void get_Prime(){  memset(is_prime,true,sizeof(is_prime));  is_prime[0]=is_prime[1]=false;  for(int i=2;i*i<maxn;i++)  {    if(is_prime[i])      prime[num_prime++]=i;    for(int j=i*i;j<=maxn;j+=i)    {      is_prime[j]=false;    }  }}long long dp[maxn]={0ll};void solve(){    get_Prime();    for(int i=2;i<maxn;++i)    {        if(is_prime[i])            dp[i]=i-1;        else        {            for(int j=0;j<num_prime;++j)                if(i%prime[j]==0)                {                    if((i/prime[j])%prime[j]==0)                        dp[i]=dp[i/prime[j]]*prime[j];                    else                        dp[i]=dp[i/prime[j]]*(prime[j]-1);                    break;                }        }    }    for(int i=3;i<maxn;++i)        dp[i]+=dp[i-1];}int main(void){  int n;  solve();  while(scanf("%d",&n)==1 && n)  {        printf("%lld\n",dp[n]);  }  return 0;}


借鉴了两篇别人的文章: 

关于欧拉函数的递推方法的证明

欧拉函数与欧拉定理


原创粉丝点击