【7】欧拉函数学习笔记

前言

这篇博客 中分离出来的。

欧拉函数是一个常用且好用的数论函数,有许多神奇的性质。

欧拉函数

欧拉函数:对正整数 n ,欧拉函数是小于 n 的正整数中与 n 互质的数的数目,记作 φ(n)

欧拉函数的性质:

1φ(1)=1

2φ(p)=p1( p 为质数)

3φ(pk)=pkpk1=(p1)pk1

4:欧拉函数是积性函数,若 m,n 互质则有 φ(m×n)=φ(m)×φ(n)

5:对于任意的 n=p1k1×p2k2×p3k3×...×prkr(其中 p1k1,p2k2,p3k3,...prkrn 的不同素因子),则有

φ(n)=n×i=1r(11pi)

对于性质 5,我们可以感性理解。就相当于枚举 n 的每个质因数,把小于 n 且有这个因数的数除去,剩下的就是互质的数。

依据性质 5,我们可以得出单个求欧拉函数的算法。

n 进行质因数分解,按照性质 5 的公式进行计算。实际上就是把式子用代码实现了而已。时间复杂度 O(n)

long long phi(long long x)
{
	long long ans=x;
	for(long long i=2;i*i<=x;i++)
	    if(x%i==0)
	       {
	       ans=ans/i*(i-1);
	       while(x%i==0)x/=i;
	       }
	if(x!=1)ans=ans/x*(x-1);
	return ans;
}

我们可以在线性筛的过程中,线性预处理出 1n 个数的欧拉函数值。

对于性质 4,我们知道欧拉函数是积性函数,所以如果我们知道了 φ(n) 的值,并知道一个 n 中不存在的质数 m,这两个数就一定互质,就可以用 φ(n)×φ(m) 得到 mn 的欧拉函数值。

根据性质 2φ(mn)=φ(n)×(m1)

在线性筛的过程中,我们有时确实会出现这种情况。当我们由点 n 乘以其中不存在的质因数 m 筛掉点 mn 时,可以顺便求出 φ(mn)。但如果不互质,也就是点 n 中存在质因数 m,我们直接将 φ(n) 乘以 m 即可得到 φ(mn)。根据性质 5,这个 m 并没有导致出现新的质因数,所以乘积式的后一项累乘式结果不变,但是前面的 n 变成了 mn,所以总体就乘以了 m。这种情况下,线性筛对点 n 的扩展也刚好结束。

由于我们从小到大枚举,且递推式中没有出现后面的值,每一次都知道 φ(n),满足递推的条件。时间复杂度 O(n),均摊 O(1)

void linear(long long n)
{
	a[1]=1;phi[1]=1;
	for(long long i=2;i<=n;i++)
	    {
	    	if(!a[i])pri[++cnt]=i,phi[i]=i-1;
	    	for(long long j=1;j<=cnt&&i*pri[j]<=n;j++)
	    	    {
	    	    a[i*pri[j]]=1;
				phi[i*pri[j]]=phi[i]*(pri[j]-1);
				if(i%pri[j]==0)
	    	       {
				   phi[i*pri[j]]=phi[i]*pri[j];
				   break;
			       }
	    	    }
		}
}

重要性质

dnφ(d)=n

感性理解,记忆即可,证明我不会。

这个性质常用来简化式子,优化复杂度。多用于处理 gcd 相关问题。例如下面这个式子:

i=1nj=1ngcd(i,j)

求解这个式子,时间复杂度为 O(n2)

套用性质,得到这个式子:

i=1nj=1ndgcd(i,j)φ(d)

将含 gcd 的式子按照 gcd 的性质(若 dgcd(x,y),则 dx,dy)展开,得到:

i=1nj=1ndi,djφ(d)

交换枚举顺序,我们先枚举 d,问题就变成了求 d 等于某值时满足条件的 i,j 的数量,用这个数量乘以 φ(d) 就能求出这一部分的答案,最后累加即可。n 以内有 nd 个数满足 di,因为每 d 个就有一个数满足条件。j 的数量也是同理。根据乘法原理,总共有 nd2 个数满足条件,所以得到:

d=1nφ(d)nd2

时间复杂度为 O(n)

例题

例题 1

UVA10179 Irreducable Basic Fractions

根据约分的定义,我们发现分母为 n 的不可约简单分数的数量就是与 n 互质的小于 n 的数的数量。这样就转化为了欧拉函数模板题,每次询问单独求解即可。

#include <bits/stdc++.h>
using namespace std;
int n,a;
bool flag=0;
int eulerfun(int a)
{
    int ans=a;
    for(int i=2;i*i<=a;i++)
        if(a%i==0)
           {
            ans=ans/i*(i-1);
            while(a%i==0)
              a/=i;
           }
    if(a!=1)ans=ans/a*(a-1);
    return ans;
}
 
int main()
{
    while(scanf("%d",&a)!=0)
         {
         if(a==0)return 0;
         if(flag)printf("%d\n",eulerfun(a));
         else printf("%d\n",eulerfun(a));
         flag=1;
         }
    return 0;
}

例题 2

P2158 [SDOI2008] 仪仗队

由我们生活中的经验或者看图说话,所以如果一个人在点 (x,y),如果 gcd(x,y)=1,他就能被看到。

我们先假定 x<y,则问题转化为了小于 n 的正整数中与 n 互质的数的数量,这就是欧拉函数的定义。我们线性处理出 1n 的欧拉函数值,然后求和,就得出了这一部分的答案。

由于 x 可能大于 y,我们也需要考虑。思考之后我们发现只需要把上一部分中的 x,y 互换即可。上一部分中每一个满足条件的数值都对应这个部分中的一个数值,答案需要乘以 2

当然,也需要考虑 x=y 的情况。很显然,这种情况下只有 (1,1) 会被看到,答案还需要加 1

注意特判 n=1 的情况。

#include <bits/stdc++.h>
using namespace std;
int n,pri[1000001],f[1000001];
bool a[1000001];
int linear(bool a[],int pri[],int n)
{
    int cnt=0;
    a[1]=1;f[1]=1;
    for(int i=2;i<=n;i++)
        {
            if(!a[i])
                {
                pri[cnt++]=i;
                f[i]=i-1;
                }
            for(int j=0;pri[j]<=n/i;j++)
                {
                    a[i*pri[j]]=1;
                    if(i%pri[j]==0)
                      {
                      f[i*pri[j]]=f[i]*pri[j];
                      break;
                      }
                    f[i*pri[j]]=f[i]*(pri[j]-1);
                }
        }
    return cnt;
}
 
int main()
{
    long long ans=0;
    scanf("%d",&n);
    if(n==1)
       {
	   printf("0");
	   return 0;
       }
    linear(a,pri,n-1);
    for(int i=1;i<=n-1;i++)
        ans+=f[i];
    printf("%lld",ans*2+1);
    return 0;
}

例题 3

P2398 GCD SUM

同重要性质部分,不多赘述。

#include <bits/stdc++.h>
using namespace std;
long long n,pri[100000001],f[100000001];
bool a[100000001];
int linear(bool a[],long long pri[],long long n)
{
    int cnt=0;
    a[1]=1;f[1]=1;
    for(int i=2;i<=n;i++)
        {
            if(!a[i])
                {
                pri[cnt++]=i;
                f[i]=i-1;
                }
            for(int j=0;pri[j]<=n/i;j++)
                {
                    a[i*pri[j]]=1;
                    if(i%pri[j]==0)
                      {
                      f[i*pri[j]]=f[i]*pri[j];
                      break;
                      }
                    f[i*pri[j]]=f[i]*(pri[j]-1);
                }
        }
    return cnt;
}
  
int main()
{
    long long ans=0;
    scanf("%lld",&n);
    linear(a,pri,n);
    for(int i=1;i<=n;i++)
        ans+=(f[i]*(n/i)*(n/i));
    printf("%lld",ans);
    return 0;
}

例题 4

P2303 [SDOI2012] Longge 的问题

很套路的进行转化,如下:

i=1ngcd(i,n)

套用重要性质,得到这个式子:

i=1ndgcd(i,n)φ(d)

将含 gcd 的式子按照 gcd 的性质(若 dgcd(x,y),则 dx,dy)展开,得到:

i=1ndi,dnφ(d)

交换枚举顺序,我们先枚举 d,问题就变成了求 d 等于某值时满足条件的 i 的数量,用这个数量乘以 φ(d) 就能求出这一部分的答案,最后累加即可。n 以内有 nd 个数满足 di,因为每 d 个就有一个数满足条件。由于还有额外条件 dn,所以我们把这个条件放在外层,得到:

dnφ(d)nd

我们可以在枚举因数的同时满足 dn 的条件,只需要枚举 n 之内的数。如果 nd 不等于 d,那么就再计算一次 nd 的情况。由于 d 的范围很大,不能线性筛,所以每次单独计算欧拉函数值。时间复杂度为 O(因数个数×n)

#include <bits/stdc++.h>
using namespace std;
long long n,ans=0;
long long phi(long long x)
{
	long long ans=x;
	for(long long i=2;i*i<=x;i++)
	    if(x%i==0)
	       {
	       ans=ans/i*(i-1);
	       while(x%i==0)x/=i;
	       }
	if(x!=1)ans=ans/x*(x-1);
	return ans;
}

int main()
{
	scanf("%lld",&n);
	for(long long i=1;i*i<=n;i++)
	    if(n%i==0)
		    {
		    	ans+=phi(i)*(n/i);
		    	if(n/i!=i)ans+=phi(n/i)*(n/(n/i));
			}
	printf("%lld\n",ans);
	return 0;
}

例题 5

P1447 [NOI2010] 能量采集

由我们生活中的经验或者看图说话,我们得到答案为这个式子:

2i=1nj=1mgcd(i,j)mn

非常套路的进行转化,套用重要性质,得到这个式子:

2i=1nj=1mdgcd(i,j)φ(d)mn

将含 gcd 的式子按照 gcd 的性质(若 dgcd(x,y),则 dx,dy)展开,得到:

2i=1nj=1mdi,djφ(d)mn

交换枚举顺序,我们先枚举 d,问题就变成了求 d 等于某值时满足条件的 i,j 的数量,用这个数量乘以 φ(d) 就能求出这一部分的答案,最后累加即可。n 以内有 nd 个数满足 di,因为每 d 个就有一个数满足条件。j 的数量也是同理,有 md 个。根据乘法原理,总共有 ndmd 个数满足条件。注意这里不需要枚举大于 m,n 中任意一值的部分,因为向下取整,这一部分必定为 0,所以得到:

=2d=1min(n,m)φ(d)ndmdmn

#include <bits/stdc++.h>
using namespace std;
long long n,m,pri[1000010],phi[1000010],cnt,ans;
bool a[1000010];
void linear(long long n)
{
	a[1]=1;phi[1]=1;
	for(long long i=2;i<=n;i++)
	    {
	    	if(!a[i])pri[++cnt]=i,phi[i]=i-1;
	    	for(long long j=1;j<=cnt&&i*pri[j]<=n;j++)
	    	    {
	    	    a[i*pri[j]]=1;
				phi[i*pri[j]]=phi[i]*(pri[j]-1);
				if(i%pri[j]==0)
	    	       {
				   phi[i*pri[j]]=phi[i]*pri[j];
				   break;
			       }
	    	    }
		}
}

int main()
{
	scanf("%lld%lld",&n,&m);
	linear(max(n,m));
	for(int i=1;i<=min(n,m);i++)ans+=phi[i]*(n/i)*(m/i);
	printf("%lld\n",ans*2-m*n);
	return 0;
}

后记

熬到半夜准备打CF,结果一看时间是昨天,一气之下写的博客。

posted @   w9095  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示