欧拉函数

Posted FallDream

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了欧拉函数相关的知识,希望对你有一定的参考价值。

Phi(x)表示小于等于x的数中与x互质的数的个数。

x的质因数是k1,k2…   则Phi(x)=x*(1-1/k)*(1-1/k2)*  …

例如Phi(6)=6*(1-1/3)*(1-1/2) =2

1.很显然,一个质数的Phi值等于它-1

2.如果x是质数的话 Phi(x^k)= x^k-x^k/x=x^k-x^(k-1),因为和这个数不互质的只有x的倍数。

3.欧拉函数是积性函数,如果把x因数分解后x=k1^p1*k2^p2*…*kn^pn

那么Phi(x)=Phi(k1^p1)*Phi(k2^p2)+...+Phi(kn^pn)

这个式子就可以用2中的公式快速计算。

4.假设j是i的质因数

那么i整除j^2时Phi(i)=phi(i/j)*j  因为Phi(i/j)中就已经计算了(1-1/j)

否则i不能整除时j^2时Phi(i)=Phi(i/j)*(j-1) 因为Phi(i/j)中不曾计算(1-1/j),

计算:1.√n暴力 2.筛法的时候,对质数k都枚举它的倍数x之后phi(x)=phi(x)/k*(k-1),复杂度是On的

实现:

1.模板题  hdu2824 求∑Phi(x) l<=x<=r  l,r<=3000000

#include<iostream>
#include<cstdio>
#define ll long long
#define MAXN 3000000
using namespace std;
ll phi[MAXN+5];
int l,r;
int main()
{
    for(int i=1;i<=MAXN;i++)phi[i]=i;
    for(int i=2;i<=MAXN;i++) if(phi[i]==i)
    {   phi[i]=i-1;
        for(int j=i<<1;j<=MAXN;j+=i)
            phi[j]=phi[j]/i*(i-1);
    }
    for(int i=1;i<=MAXN;i++)phi[i]+=phi[i-1];
    while(scanf("%d%d",&l,&r)!=EOF)
       cout<<phi[r]-phi[l-1]<<endl;
    return 0;
}

2.poj2480 Longge‘s problem

题意:求∑gcd(i,N) 1<=i<=N  N是int

题解:

对于每个它的因数k,gcd(x,N) 的x的个数为Phi(x/k),贡献为k*Phi(x/k),所以可以直接暴力,复杂度大概是√n*因数个数。

那有没有更优秀的做法呢?当然是有的。

设f[n]为题目所求的∑gcd(i,N)  因为gcd是积性函数,又根据一个结论:积性函数求和还是积性函数,所以f(x)也是积性函数。

我们可以把n分解成p1^k1*p2^k2*....*pm^km,则f[n]=f[p1^k1]*...*f[pm^km]

根据f[x]=∑k*Phi(x/k)  k|x 我们可以得到:f[p^k]=∑p^i*Phi(P^(k-i)) i=0..k  根据第二点化简一下:

f[p^k]=∑ p^i*(P^(k-i)-P^(k-i-1))(i=1..k)+p^k  

    =∑(p^k-P^(k-1))(i=1..k)+p^k

    =(k+1)*p^k-k*p^(k-1)

这样只要直接因数分解就能非常快的算出答案,最后把所有的f值乘起来就可以了。

#include<iostream>
#include<cstdio>
#define MAXN 100000
#define ll long long
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<0||ch>9){if(ch==-) f=-1;ch=getchar();}
    while(ch>=0&&ch<=9){x=x*10+ch-0; ch=getchar();}
    return x*f;
}

int n,cnt=0;
ll ans=1;
int s[MAXN+5];
bool b[MAXN+5];

int pow(int x,int p)
{
    int sum=1;
    for(int i=x;p;p>>=1,i*=i)if(p&1)sum=sum*i;
    return sum;
}

int main()
{
    n=read();
    for(int i=2;i<=MAXN;i++)
    {
        if(!b[i])s[++cnt]=i;
        for(int j=1;j<=cnt&&s[j]*i<=MAXN;j++)
        {
            b[s[j]*i]=1;
            if(i%s[j]==0)break;
        }
    }
    for(int i=1;i<=cnt;i++)
        if(n%s[i]==0)
        {
            int num=0;
            while(n%s[i]==0)
            {n/=s[i];num++;}
            ans*=1LL*(num+1)*pow(s[i],num)-1LL*num*pow(s[i],num-1);
        }
    if(n>1)ans*=2*n-1;
    cout<<ans;
    return 0;
}

3.欧拉函数前缀和

原题是这样的:有个n*n的矩阵,每个点有一个灭的灯泡。

你每次可以按下一个灯泡(x,y)的开关,这样的话所有(x*k,y*k),1<=k<=n/x 的灯泡状态都会改变。

就比如n=4,你按下(2,1)就会改变(2,1),(4,2)的状态。

求你最少要按下多少次,可以让所有的灯都亮起来。

题解:

我们发现,对于每个不同的x/y我们都只会按一次,所以我们只会去按那些xy互质的点。

题目转化为有多少对(x,y)是互质的,即(∑Phi(i),2<=i<=n)*2+1(也可以假装Phi(1)是1),就是一道欧拉函数前缀和题目,但n有10^9那么大,无法暴力。

那么我们用F(x)表示有多少个gcd(x,y)=1 1<=y<=x的个数,那么gcd(x,y)=2的个数为F(x/2),gcd=3的个数为F(x/3)....

由于状态总共有x^2种 所以F(x)+F(x/2)+F(x/3)+...+F(x/x)=n^2 

也就是说F(x)=n^2-F(x/2)-F(x/3)-...-F(1)   

F(i)的i最多只有√n种,所以可以记忆化搜索,据说复杂度是n^(2/3),很科学。

但是这题貌似是某高校的contest,是Private的,所以你也没处交.....所以代码就假装过了懒得写了...

 

以上是关于欧拉函数的主要内容,如果未能解决你的问题,请参考以下文章

欧拉Euler函数

HDU 2588 GCD(欧拉函数)

蓝桥杯必备算法一:欧拉函数

数论之旅4---欧拉函数的证明及代码实现(我会证明都是骗人的╮( ̄▽ ̄)╭)

欧拉函数性质与求法 [数论][欧拉函数]

欧拉筛欧拉函数