欧拉函数

Part 1.什么是欧拉函数?

在数论,对正整数 \(n\) ,欧拉函数是小于或等于 \(n\) 的正整数中与 \(n\) 互质的数的数目(因此 \(\varphi(1)=1\))。此函数以其首名研究者欧拉命名 (Euler) ,它又称为 Euler's totient function、\(\varphi\) 函数、欧拉商数等。——百度百科


Part 2.一些性质

  1. \(n\) 为质数,则 \(\varphi(n)=n-1\)

  2. \(\forall n>1\)\([1,n]\) 中与 \(n\) 互质的数的和为 \(\dfrac{n\times\varphi(n)}{2}\)

  3. \(a,b\) 互质,则 \(\varphi(a\times b)=\varphi(a)\times\varphi(b)\)

  4. \(a,m\) 互质,则 \(a^{\varphi(m)}\equiv 1\pmod m\)

  5. \(\sum_{d\mid n}\varphi(d)=n\)

  6. \(n\bmod p=0\),则 \(\varphi(n\times p)=\varphi(n)\times p\)。 若 \(n\bmod p\not= 0\),则 \(\varphi(n\times p)=\varphi(n)\times (p-1)\)


Part 3.怎么求?

1.通式

假设正整数 \(n\)\(m\) 个质因数,分别为 \(p_1,p_2,p_3,\cdots,p_m\),则 \(\varphi(n)\) 有以下通式:

\[\varphi(n)=n\times(1-\frac{1}{p_1})\times(1-\frac{1}{p_2})\times(1-\frac{1}{p_3})\times\cdots\times(1-\frac{1}{p_m}) \]

即:

\[\varphi(n)=n\times\prod_{i=1}^{m} (1-\frac{1}{p_i}) \]

推导过程的话,懒得打了...放个链接吧。

根据通式我们可以写出以下代码:

int euler(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.若a为质数,phi[a]=a-1;
2.若a为质数,b mod a=0,phi[a*b]=phi[b]*a
3.若a,b互质,phi[a*b]=phi[a]*phi[b](当a为质数时,if b mod a!=0 ,phi[a*b]=phi[a]*phi[b])
*/
int m[n],phi[n],p[n],nump;
//m[i]标记i是否为素数,0为素数,1不为素数;p是存放素数的数组;nump是当前素数个数;phi[i]为欧拉函数
int main()
{
    phi[1]=1;
    for (int i=2;i<=n;i++)
    {
        if (!m[i])//i为素数
        {
            p[++nump]=i;//将i加入素数数组p中
            phi[i]=i-1;//因为i是素数,由特性得知    
        }    
        for (int j=1;j<=nump&&p[j]*i<=n;j++)  //用当前已得到的素数数组p筛,筛去p[j]*i
        {
            m[p[j]*i]=1;//可以确定i*p[j]不是素数 
            if (i%p[j]==0) //看p[j]是否是i的约数,因为素数p[j],等于判断i和p[j]是否互质 
            {
                phi[p[j]*i]=phi[i]*p[j]; //特性2
                break;
            }
            else phi[p[j]*i]=phi[i]*(p[j]-1); //互质,特性3其,p[j]-1就是phi[p[j]]   
        }
    }
}

Part 4.例题

(1)SPOJ4141 Euler Totient Function

SPOJ题目链接

洛谷题目链接

模板题,不做分析。

#include<iostream>
#include<cstdio>
using namespace std;
int euler(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;
}
int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        int n;
        cin>>n;
        cout<<euler(n)<<endl;
    }
    return 0;
}

(2)HDU2588 GCD

题目链接

\(p=\gcd(x,n),\;n=p\times a,\;x=p\times b\),显然 \(a\)\(b\) 一定互质。

所以我们可以枚举公约数 \(p\),通过 \(n\div p\) 求出 \(a\),然后求区间 \([1,a]\) 内有多少个与 \(a\) 互质的 \(b\) 即可。

我们可以用上面的欧拉函数 \(\varphi(a)\) 来求与 \(a\) 互质的数。

code:

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
int gcd(int a,int b) {return !b?a:gcd(b,a%b);}
ll euler(ll n) 
{
    ll 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;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
    	int n,m;
        scanf("%d%d",&n,&m);
        ll ans=0;
        for(int i=1;i*i<=n;i++)
        {
            if(n%i==0) //i或者n/i为公约数的情况
            {
                if(i>=m) ans+=euler(n/i); //如果i为公约数
                if(i*i!=n && n/i>=m) ans+=euler(i); //如果n/i为公约数
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

(3)BZOJ2818 Gcd

题目链接

\(\gcd(x,y)=k\),则有 \(\gcd(x\div k,y\div k)=1\)

此时我们只需求区间 \([1,n\div p_i]\) 内有多少个互质的 \((x,y)\) 即可。

\((x,y)\)\((y,x)\) 视为两个不同的二元组,所以只需求出 \((x,y)\) 即可,之后再 \(\times 2\)。可是,如果按上述算法计算,\((1,1)\) 会重复算两遍,所以要减去 \(1\)

code:

#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
const int N=10000005;
int n,p,cnt,phi[N],pri[N];
bool book[N]; 
ll ans,sum[N];
void getphi() //筛法求φ(1~n)顺便求1~n的所有质数
{
	phi[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!book[i])
		{
			phi[i]=i-1;
			pri[++cnt]=i;
		}
		for(int j=1;j<=cnt && i*pri[j]<=n;j++)
		{
			book[i*pri[j]]=1;
			if(i%pri[j]==0)
			{
				phi[i*pri[j]]=phi[i]*pri[j];
				break;
			}
			else phi[i*pri[j]]=phi[i]*phi[pri[j]];
		}
	}
}
int main()
{
	scanf("%d",&n);
	getphi();
	for(int i=1;i<=n;i++) sum[i]=sum[i-1]+phi[i];
	for(int i=1;i<=cnt;i++) ans+=sum[n/pri[i]]*2-1;
	printf("%lld",ans);
	return 0;
}
posted @ 2020-02-14 09:41  zzt1208  阅读(326)  评论(0编辑  收藏  举报