【7】欧拉函数学习笔记

前言

这篇博客 中分离出来的。

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

欧拉函数

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

欧拉函数的性质:

\(1\)\(\varphi(1)=1\)

\(2\)\(\varphi(p)=p-1\)( \(p\) 为质数)

\(3\)\(\varphi(p^k)=p^k-p^{k-1}=(p-1)*p^{k-1}\)

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

\(5\):对于任意的 \(n=p_{1}^{k_{1}}\times p_{2}^{k_{2}}\times p_{3}^{k_{3}}\times ... \times p_{r}^{k_{r}}\)(其中 \(p_{1}^{k_{1}},p_{2}^{k_{2}},p_{3}^{k_{3}},...p_{r}^{k_{r}}\)\(n\) 的不同素因子),则有

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

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

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

\(n\) 进行质因数分解,按照性质 \(5\) 的公式进行计算。实际上就是把式子用代码实现了而已。时间复杂度 \(O(\sqrt{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;
}

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

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

根据性质 \(2\)\(\varphi(mn)=\varphi(n)\times(m-1)\)

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

由于我们从小到大枚举,且递推式中没有出现后面的值,每一次都知道 \(\varphi(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;
			       }
	    	    }
		}
}

重要性质

\[\sum_{d\mid n}\varphi(d)=n \]

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

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

\[\sum_{i=1}^n\sum_{j=1}^n\gcd(i,j) \]

求解这个式子,时间复杂度为 \(O(n^2)\)

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

\[\sum_{i=1}^n\sum_{j=1}^n\sum_{d\mid gcd(i,j)}\varphi(d) \]

将含 \(\gcd\) 的式子按照 \(\gcd\) 的性质(若 \(d\mid\gcd(x,y)\),则 \(d\mid x,d\mid y\))展开,得到:

\[\sum_{i=1}^n\sum_{j=1}^n\sum_{d\mid i,d\mid j}\varphi(d) \]

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

\[\sum_{d=1}^n\varphi(d)\lfloor\frac{n}{d}\rfloor^{2} \]

时间复杂度为 \(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\lt y\),则问题转化为了小于 \(n\) 的正整数中与 \(n\) 互质的数的数量,这就是欧拉函数的定义。我们线性处理出 \(1\sim n\) 的欧拉函数值,然后求和,就得出了这一部分的答案。

由于 \(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 的问题

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

\[\sum_{i=1}^{n}\gcd(i,n) \]

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

\[\sum_{i=1}^{n}\sum_{d\mid \gcd(i,n)}\varphi(d) \]

将含 \(\gcd\) 的式子按照 \(\gcd\) 的性质(若 \(d\mid\gcd(x,y)\),则 \(d\mid x,d\mid y\))展开,得到:

\[\sum_{i=1}^{n}\sum_{d\mid i,d\mid n}\varphi(d) \]

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

\[\sum_{d\mid n}\varphi(d)\lfloor\frac{n}{d}\rfloor \]

我们可以在枚举因数的同时满足 \(d\mid n\) 的条件,只需要枚举 \(\sqrt{n}\) 之内的数。如果 \(\frac{n}{d}\) 不等于 \(d\),那么就再计算一次 \(\frac{n}{d}\) 的情况。由于 \(d\) 的范围很大,不能线性筛,所以每次单独计算欧拉函数值。时间复杂度为 \(O(\text{因数个数}\times \sqrt{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] 能量采集

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

\[2\sum_{i=1}^{n}\sum_{j=1}^{m}\gcd(i,j)-mn \]

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

\[2\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{d\mid \gcd(i,j)}\varphi(d)-mn \]

将含 \(\gcd\) 的式子按照 \(\gcd\) 的性质(若 \(d\mid\gcd(x,y)\),则 \(d\mid x,d\mid y\))展开,得到:

\[2\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{d\mid i,d\mid j}\varphi(d)-mn \]

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

\[=2\sum_{d=1}^{\min(n,m)}\varphi(d)\lfloor\frac{n}{d}\rfloor\lfloor\frac{m}{d}\rfloor-mn \]

#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 @ 2025-02-08 14:11  w9095  阅读(69)  评论(0)    收藏  举报